1. 前言
本文是AliOS Things提供的一套C語言代碼規范,適用的對象為符合C99標準的C語言工程。
2. 命名
本節內容均為建議,不作強制要求。
2.1. 總則
各種命名均使用英文單詞及其縮寫,非特殊情況不能使用漢語拼音或其他語言。
2.2. 文件命名
文件名全部使用小寫字母,用_
連接。
源文件使用.c
后綴。
頭文件使用.h
后綴。
2.3. 類型命名
2.3.1. 簡單類型命名
使用typedef
自定義的簡單類型命名全部使用小寫字母,用_
連接,以_t
結尾。例如:
typedef int32_t aos_status_t;
2.3.2. 結構體和聯合體命名
結構體和聯合體類型命名全部使用小寫字母,用_
連接。建議使用typedef
定義一個整體的名字,以_t
結尾。例如:
typedef struct aos_list_node {
struct aos_list_node *prev;
struct aos_list_node *next;
} aos_list_node_t;
static aos_list_node_t list_node;
2.3.3. 枚舉命名
枚舉類型命名全部使用小寫字母,用_
連接。建議使用typedef
定義一個整體的名字,以_t
結尾。
枚舉值命名全部使用大寫字母,用_
連接,包含表示類型的前綴。
例如:
typedef enum aos_socket_stage {
AOS_SOCK_STG_DISCONNECTED,
AOS_SOCK_STG_CONNECTED,
} aos_socket_stage_t;
static aos_socket_stage_t sock_stage = AOS_SOCK_STG_DISCONNECTED;
2.4. 變量命名
變量命名全部使用小寫字母,用_
連接。
數組名稱盡量使用復數名詞。例如:
cfg_file_t cfg_files[NUM_CFG_FILES];
表示數目的變量名稱使用num(number的縮寫)加復數名詞。例如:
unsigned int num_files;
表示序號的變量名稱使用單數名詞加num或index或idx(index的縮寫)。例如:
unsigned int file_num;
unsigned int file_index;
2.5. 函數命名
函數命名全部使用小寫字母,用_
連接。
2.6. 宏命名
一般的宏命名全部使用大寫字母,用_
連接。例如:
#define AOS_STRING_MAX_LEN 127
模擬函數使用方式的宏的命名規則與函數相同。例如:
#define aos_dev_set_id(dev, x) \
do { \
(dev)->id = (x); \
} while (0)
2.7. 前綴
為防止命名空間污染,公用組件中的非static函數、非static全局變量、全局類型、全局宏的命名應帶有前綴。例如(假設前綴為aos
):
void aos_cfg_file_close(int fd);
extern char **aos_process_argv;
typedef struct aos_list_node aos_list_node_t;
#define AOS_STRING_MAX_LEN 127
3. 格式
3.1. 文本格式
源文件、頭文件、Makefile等文本文件一律采用UTF-8 without BOM編碼,采用Unix風格換行格式。 文本文件末尾應有且只有一個換行符,即末尾應有且只有一個空行。
3.2. 行長度
每行字符數原則上不超過120。包含長路徑的#include
語句、頭文件#define
保護可以無視此規則。
3.2.1. 表達式換行
較長的表達式可在運算符處換行,換行處的運算符屬于舊行,新行對齊到舊行中的相同邏輯層級。例如:
void foo(void)
{
if ((aos_list_next(list_node) != &list_head && !priv) ||
!(strcmp(symbol, default_symbol) && blahblahblahblahblahblah() &&
meomeomeomeomeomeomeomeomeomeomeomeomeomeomeomeo(NULL))) {
/* ... */
}
}
3.2.2. 函數換行
較長的函數定義、聲明可在返回值類型和函數名稱之間換行。若返回值為指針類型,*
屬于新行。例如:
static unsigned long
blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah(void);
static const manager_priv_t
*blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah(int index);
較長的函數定義、聲明、調用可在參數列表中間換行,參數列表中間換行后新行應縮進至舊行第一個參數處。例如:
void blahblahblahblahblahblahblahblahblah(manager_priv_t *priv, int index,
const char *proc_name);
void foo(void)
{
blahblahblahblahblahblahblahblahblahblah(get_manager_priv(manager), 0,
"meomeomeomeomeomeomeomeo");
}
3.2.3. 字符串換行
較長的字符串可在空格處換行,一般情況下換行處的空格屬于舊行。例如:
void foo(void)
{
printf("The GNU operating system consists of GNU packages "
"(programs specifically released by the GNU Project) "
"as well as free software released by third parties.\n");
}
3.3. 縮進
使用空格縮進,每次4個空格。全文不應出現制表符(tab)。例如:
void foo(unsigned int nbr_processes)
{
unsigned int i;
while (i < nbr_processes) {
const char *name;
/* ... */
}
}
宏定義、行尾注釋、結構體、聯合體、枚舉等內部可縮進實現多行對齊,但不作強制要求。若有縮進,應對齊到4的整數倍。例如:
/* 此處數字0縮進32個字符,即位于第33列。 */
#define STAGE_UPDATE_CONTINUE 0
#define STAGE_UPDATE_COMPLETE 1
/* 合法 */
#define EVENT_RX_FULL (1U << 0)
#define EVENT_TX_EMPTY (1U << 1)
typdef enum socket_stage {
/* 此處等號縮進32個字符,即位于第33列。 */
SOCK_STG_DISCONNECTED = 0,
SOCK_STG_CONNECTED = 1,
} socket_stage_t;
/* 此處反斜杠縮進40個字符,即位于第41列。 */
#define aos_dev_set_flags(dev, x) \
do { \
(dev)->flags = (x); \
} while (0)
/* 合法 */
#define aos_dev_set_ops(dev, x) \
do { \
(dev)->ops = (x); \
} while (0)
/* 此處注釋縮進24個字符,即位于第25列。 */
foo(NULL); /* abc */
blahblahblahblahblah(); /* xyz */
/* 合法 */
foofoofoo(); /* abc */
foofoo(); /* xyz */
分行定義的宏,第二行起應縮進一次。例如:
#define aos_dev_set_id(dev, x) \
do { \
(dev)->id = (x); \
} while (0)
switch
塊中的case
語句和default
語句與switch
語句縮進層級相同。例如:
switch (stage) {
case SOCK_STG_DISCONNECTED:
foo();
break;
case SOCK_STG_CONNECTED:
sock->connected = 1;
break;
default:
break;
}
3.4. 花括號
函數體的左花括號另起一行;其他情況下左花括號不另起一行。一般情況下左花括號后續內容另起一行;宏定義中、數組、結構體、聯合體初始化時若花括號中內容較短則左花括號后續內容可以不另起一行。 一般情況下右花括號另起一行;宏定義中、數組、結構體、聯合體初始化時若花括號中內容較短則右花括號可以不另起一行。右花括號與后續內容組合成一行。 例如:
typedef struct manager_priv {
int index;
void *data;
} manager_priv_t;
#define set_manager_index(x, idx) do { (x)->priv->index = (idx); } while (0)
#ifdef __cplusplus
extern "C" {
#endif
void foo(void)
{
int i = 0;
/* ... */
if (i == 0) {
/* ... */
} else {
/* ... */
}
}
manager_priv_t priv = { 0, NULL, };
#ifdef __cplusplus
}
#endif
3.5. 空格
行尾不應有空格。
三元操作符和二元操作符(獲取成員的.
和->
操作符除外)前后留有空格。例如:
x = a ? b : c;
v = w * x + y / z;
len = x.length;
priv = proc->priv;
一元操作符與參數之間不留空格。例如:
x = *p;
p = &x;
i++;
j = --i;
逗號右側若有內容,逗號與右側內容之間應有空格。例如:
void foo(int x, int y);
分號右側若有內容(右圓括號或另外一個分號除外),分號與右側內容之間應有空格。例如:
for (i = 0; i < strlen(str);) {
for (;;) {
/* ... */
}
}
圓括號內部內容與圓括號之間不留空格。例如:
len = strlen(name);
for (i = 0; i < count; i++) {
/* ... */
}
圓括號與左側關鍵字之間應有空格。例如:
while (1) {
/* ... */
}
if (i == 0) {
/* ... */
}
圓括號與左側函數名之間不留空格。例如:
int load_file(const char *name)
{
foo(0);
/* ... */
}
類型轉換中的圓括號與右側內容之間不留空格。例如:
manager_priv_t *priv = (manager_priv_t *)p;
方括號與左側內容、內部內容之間不留空格。例如:
c = name[i];
左花括號左側或右側若有內容,左右內容與左花括號之間應有空格。右花括號左側若有內容,左側內容與右花括號之間應有空格;右花括號右側若有內容(分號、逗號除外),右側內容與右花括號之間應有空格。例如:
#define set_manager_index(x, idx) do { (x)->priv->index = (idx); } while (0)
manager_priv_t priv = { 0, NULL, };
分行定義的宏,\
與左側內容之間應有空格。例如:
#define set_manager_index(x, idx) \
do { \
(x)->priv->index = (idx); \
} while (0)
3.6. 指針
指針聲明或定義時,*
應靠近變量名稱。*
與修飾符之間應有空格。例如:
int *p;
const char *name;
void * const ptr;
void (*func)(void *arg);
3.7. 數值常量
十六進制數字A
~ F
使用大寫形式。
表示二進制的前綴0b
和表示十六進制的0x
使用小寫形式。
后綴U
和L
使用大寫形式。
后綴f
使用小寫形式。
表示冪的e
和p
使用小寫形式。
例如:
unsigned int b = 0b0101;
unsigned int x = 0xABCDEF;
unsigned int u = 0U;
long int l = 0L;
unsigned long int ul = 0UL;
float f = 1.0f;
long double ld = 1.0L;
double dd = -1.5e-5;
double xd = 0xA.Bp12;
3.8. 注釋
使用C90風格的/* */
,不使用C++風格的//
。
/*
或*/
與注釋正文之間應有空格。行尾的注釋和代碼之間應有空格。
完整語句注意首字母大寫和標點符號,簡單詞組可以不使用標點。注意區分中英文標點。
TODO:使用特定注釋格式可利用doxygen等自動化工具生成文檔。
例如:
/*
* This source file is part of AliOS Things.
* Zhang San <zhangsan@example.com>
* 2021.07.01
*/
/* Zhang San <zhangsan@example.com>
* 2021.07.01 */
/* This pointer must NOT be NULL. */
/* connecting */
4. 頭文件
4.1. 路徑
為避免與第三方庫的頭文件命名沖突,公用組件的頭文件應存放于子目錄中,引用時路徑包含子目錄名稱。例如:
#include <aos/file.h>
4.2. 引號和尖括號
只有包含與本源文件處于同路徑中的頭文件時使用引號,其他情況均使用尖括號。例如:
#include <stdio.h>
#include "my_demo.h"
4.3. 包含次序
包含頭文件的次序如下: |次序 |種類| |-:- |:-| |1 |C語言標準庫頭文件和工具鏈頭文件| |2 |公用組件的頭文件| |3 |本工程頭文件|
例如:
#include <stdio.h>
#include <pthread.h>
#include <openssl/rsa.h>
#include <aos/headers.h>
#include "my_demo.h"
4.4. 保護
所有頭文件都應該使用#define
保護來防止被重復包含。相關宏命名格式是PATH_FILE_H
。
例如,頭文件aos/common.h可按如下方法保護:
#ifndef AOS_COMMON_H
#define AOS_COMMON_H
/* 全部內容 */
#endif /* AOS_COMMON_H */
4.5. 函數、變量聲明
頭文件中的函數聲明不使用extern
關鍵字。頭文件中的全局變量聲明使用extern
關鍵字。例如:
void aos_cfg_file_close(int fd);
extern char **aos_process_argv;
4.6. extern "C"關鍵字
公用頭文件中聲明的函數和全局變量應該使用extern "C"
關鍵字修飾。
#include
不應使用extern "C"
關鍵字修飾。
#define
、類型定義不作要求,可酌情考慮。
例如:
#ifndef AOS_COMMON_H
#define AOS_COMMON_H
#include <stddef.h>
#define AOS_STRING_MAX_LEN 127
#define AOS_LSTRING_MAX_LEN 511
typedef struct aos_tm {
unsigned int sec;
unsigned int min;
unsigned int hour;
unsigned int mday;
unsigned int mon;
unsigned int year;
} aos_tm_t;
#ifdef __cplusplus
extern "C" {
#endif
extern char aos_process_symbol[AOS_STRING_MAX_LEN + 1];
void aos_start(void);
#ifdef __cplusplus
}
#endif
#endif /* AOS_COMMON_H */
5. 其他注意事項
只在本編譯單元使用的函數、全局變量應使用static
修飾符。
在不影響功能的前提下,指針類型的函數參數盡量使用const
修飾符。
自增、自減運算符單獨使用時采用后置形式。
數組、結構體初始化列表、枚舉類型定義中的最后一個成員之后應有逗號。例如:
int offsets[] = {
0,
1,
};
打印到終端的信息一律使用英文,以避免終端編碼問題。