1@page coding_style 代码风格
2
3**[更正文档](https://gitee.com/alios-things/documentation/edit/rel_3.3.0/coding/coding_style.md)**      **[贡献说明](https://help.aliyun.com/document_detail/302301.html)**
4
5# 1. 前言
6本文是AliOS Things提供的一套C语言代码规范,适用的对象为符合C99标准的C语言工程。
7
8# 2. 命名
9本节内容均为建议,不作强制要求。
10
11## 2.1. 总则
12各种命名均使用英文单词及其缩写,非特殊情况不能使用汉语拼音或其他语言。
13
14## 2.2. 文件命名
15文件名全部使用小写字母,用`_`连接。
16源文件使用`.c`后缀。
17头文件使用`.h`后缀。
18
19## 2.3. 类型命名
20### 2.3.1. 简单类型命名
21使用`typedef`自定义的简单类型命名全部使用小写字母,用`_`连接,以`_t`结尾。例如:
22```c
23typedef int32_t aos_status_t;
24```
25
26### 2.3.2. 结构体和联合体命名
27结构体和联合体类型命名全部使用小写字母,用`_`连接。建议使用`typedef`定义一个整体的名字,以`_t`结尾。例如:
28```c
29typedef struct aos_list_node {
30    struct aos_list_node *prev;
31    struct aos_list_node *next;
32} aos_list_node_t;
33
34static aos_list_node_t list_node;
35```
36
37### 2.3.3. 枚举命名
38枚举类型命名全部使用小写字母,用`_`连接。建议使用`typedef`定义一个整体的名字,以`_t`结尾。
39枚举值命名全部使用大写字母,用`_`连接,包含表示类型的前缀。
40例如:
41```c
42typedef enum aos_socket_stage {
43    AOS_SOCK_STG_DISCONNECTED,
44    AOS_SOCK_STG_CONNECTED,
45} aos_socket_stage_t;
46
47static aos_socket_stage_t sock_stage = AOS_SOCK_STG_DISCONNECTED;
48```
49
50## 2.4. 变量命名
51变量命名全部使用小写字母,用`_`连接。
52数组名称尽量使用复数名词。例如:
53```c
54cfg_file_t cfg_files[NUM_CFG_FILES];
55```
56表示数目的变量名称使用num(number的缩写)加复数名词。例如:
57```c
58unsigned int num_files;
59```
60表示序号的变量名称使用单数名词加num或index或idx(index的缩写)。例如:
61```c
62unsigned int file_num;
63unsigned int file_index;
64```
65
66## 2.5. 函数命名
67函数命名全部使用小写字母,用`_`连接。
68
69## 2.6. 宏命名
70一般的宏命名全部使用大写字母,用`_`连接。例如:
71```c
72#define AOS_STRING_MAX_LEN 127
73```
74模拟函数使用方式的宏的命名规则与函数相同。例如:
75```c
76#define aos_dev_set_id(dev, x) \
77    do { \
78        (dev)->id = (x); \
79    } while (0)
80```
81## 2.7. 前缀
82为防止命名空间污染,公用组件中的非static函数、非static全局变量、全局类型、全局宏的命名应带有前缀。例如(假设前缀为`aos`):
83```c
84void aos_cfg_file_close(int fd);
85extern char **aos_process_argv;
86typedef struct aos_list_node aos_list_node_t;
87#define AOS_STRING_MAX_LEN 127
88```
89
90# 3. 格式
91## 3.1. 文本格式
92源文件、头文件、Makefile等文本文件一律采用UTF-8 without BOM编码,采用Unix风格换行格式。
93文本文件末尾应有且只有一个换行符,即末尾应有且只有一个空行。
94
95## 3.2. 行长度
96每行字符数原则上不超过120。包含长路径的`#include`语句、头文件`#define`保护可以无视此规则。
97
98### 3.2.1. 表达式换行
99较长的表达式可在运算符处换行,换行处的运算符属于旧行,新行对齐到旧行中的相同逻辑层级。例如:
100```c
101void foo(void)
102{
103    if ((aos_list_next(list_node) != &list_head && !priv) ||
104        !(strcmp(symbol, default_symbol) && blahblahblahblahblahblah() &&
105          meomeomeomeomeomeomeomeomeomeomeomeomeomeomeomeo(NULL))) {
106        /* ... */
107    }
108}
109```
110
111### 3.2.2. 函数换行
112较长的函数定义、声明可在返回值类型和函数名称之间换行。若返回值为指针类型,`*`属于新行。例如:
113```c
114static unsigned long
115blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah(void);
116static const manager_priv_t
117*blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah(int index);
118```
119较长的函数定义、声明、调用可在参数列表中间换行,参数列表中间换行后新行应缩进至旧行第一个参数处。例如:
120```c
121void blahblahblahblahblahblahblahblahblah(manager_priv_t *priv, int index,
122                                          const char *proc_name);
123
124void foo(void)
125{
126    blahblahblahblahblahblahblahblahblahblah(get_manager_priv(manager), 0,
127                                             "meomeomeomeomeomeomeomeo");
128}
129```
130
131### 3.2.3. 字符串换行
132较长的字符串可在空格处换行,一般情况下换行处的空格属于旧行。例如:
133```c
134void foo(void)
135{
136    printf("The GNU operating system consists of GNU packages "
137           "(programs specifically released by the GNU Project) "
138           "as well as free software released by third parties.\n");
139}
140```
141
142## 3.3. 缩进
143使用空格缩进,每次4个空格。全文不应出现制表符(tab)。例如:
144```c
145void foo(unsigned int nbr_processes)
146{
147    unsigned int i;
148
149    while (i < nbr_processes) {
150        const char *name;
151        /* ... */
152    }
153}
154```
155宏定义、行尾注释、结构体、联合体、枚举等内部可缩进实现多行对齐,但不作强制要求。若有缩进,应对齐到4的整数倍。例如:
156```c
157/* 此处数字0缩进32个字符,即位于第33列。 */
158#define STAGE_UPDATE_CONTINUE   0
159#define STAGE_UPDATE_COMPLETE   1
160
161/* 合法 */
162#define EVENT_RX_FULL (1U << 0)
163#define EVENT_TX_EMPTY (1U << 1)
164
165typdef enum socket_stage {
166    /* 此处等号缩进32个字符,即位于第33列。 */
167    SOCK_STG_DISCONNECTED       = 0,
168    SOCK_STG_CONNECTED          = 1,
169} socket_stage_t;
170
171/* 此处反斜杠缩进40个字符,即位于第41列。 */
172#define aos_dev_set_flags(dev, x)       \
173    do {                                \
174        (dev)->flags = (x);             \
175    } while (0)
176
177/* 合法 */
178#define aos_dev_set_ops(dev, x) \
179    do { \
180        (dev)->ops = (x); \
181    } while (0)
182
183/* 此处注释缩进24个字符,即位于第25列。 */
184foo(NULL);              /* abc */
185blahblahblahblahblah(); /* xyz */
186
187/* 合法 */
188foofoofoo(); /* abc */
189foofoo(); /* xyz */
190```
191分行定义的宏,第二行起应缩进一次。例如:
192```c
193#define aos_dev_set_id(dev, x) \
194    do { \
195        (dev)->id = (x); \
196    } while (0)
197```
198`switch`块中的`case`语句和`default`语句与`switch`语句缩进层级相同。例如:
199```c
200switch (stage) {
201case SOCK_STG_DISCONNECTED:
202    foo();
203    break;
204case SOCK_STG_CONNECTED:
205    sock->connected = 1;
206    break;
207default:
208    break;
209}
210```
211
212## 3.4. 花括号
213函数体的左花括号另起一行;其他情况下左花括号不另起一行。一般情况下左花括号后续内容另起一行;宏定义中、数组、结构体、联合体初始化时若花括号中内容较短则左花括号后续内容可以不另起一行。
214一般情况下右花括号另起一行;宏定义中、数组、结构体、联合体初始化时若花括号中内容较短则右花括号可以不另起一行。右花括号与后续内容组合成一行。
215例如:
216```c
217typedef struct manager_priv {
218    int index;
219    void *data;
220} manager_priv_t;
221
222#define set_manager_index(x, idx) do { (x)->priv->index = (idx); } while (0)
223
224#ifdef __cplusplus
225extern "C" {
226#endif
227
228void foo(void)
229{
230    int i = 0;
231
232    /* ... */
233
234    if (i == 0) {
235        /* ... */
236    } else {
237        /* ... */
238    }
239}
240
241manager_priv_t priv = { 0, NULL, };
242
243#ifdef __cplusplus
244}
245#endif
246```
247
248## 3.5. 空格
249行尾不应有空格。
250三元操作符和二元操作符(获取成员的`.`和`->`操作符除外)前后留有空格。例如:
251```c
252x = a ? b : c;
253v = w * x + y / z;
254len = x.length;
255priv = proc->priv;
256```
257一元操作符与参数之间不留空格。例如:
258```c
259x = *p;
260p = &x;
261i++;
262j = --i;
263```
264逗号右侧若有内容,逗号与右侧内容之间应有空格。例如:
265```c
266void foo(int x, int y);
267```
268分号右侧若有内容(右圆括号或另外一个分号除外),分号与右侧内容之间应有空格。例如:
269```c
270for (i = 0; i < strlen(str);) {
271    for (;;) {
272        /* ... */
273    }
274}
275```
276圆括号内部内容与圆括号之间不留空格。例如:
277```c
278len = strlen(name);
279
280for (i = 0; i < count; i++) {
281    /* ... */
282}
283```
284圆括号与左侧关键字之间应有空格。例如:
285```c
286while (1) {
287    /* ... */
288}
289
290if (i == 0) {
291    /* ... */
292}
293```
294圆括号与左侧函数名之间不留空格。例如:
295```c
296int load_file(const char *name)
297{
298    foo(0);
299    /* ... */
300}
301```
302类型转换中的圆括号与右侧内容之间不留空格。例如:
303```c
304manager_priv_t *priv = (manager_priv_t *)p;
305```
306方括号与左侧内容、内部内容之间不留空格。例如:
307```c
308c = name[i];
309```
310左花括号左侧或右侧若有内容,左右内容与左花括号之间应有空格。右花括号左侧若有内容,左侧内容与右花括号之间应有空格;右花括号右侧若有内容(分号、逗号除外),右侧内容与右花括号之间应有空格。例如:
311```c
312#define set_manager_index(x, idx) do { (x)->priv->index = (idx); } while (0)
313
314manager_priv_t priv = { 0, NULL, };
315```
316分行定义的宏,`\`与左侧内容之间应有空格。例如:
317```c
318#define set_manager_index(x, idx) \
319    do { \
320        (x)->priv->index = (idx); \
321    } while (0)
322```
323
324## 3.6. 指针
325指针声明或定义时,`*`应靠近变量名称。`*`与修饰符之间应有空格。例如:
326```c
327int *p;
328const char *name;
329void * const ptr;
330void (*func)(void *arg);
331```
332
333## 3.7. 数值常量
334十六进制数字`A` ~ `F`使用大写形式。
335表示二进制的前缀`0b`和表示十六进制的`0x`使用小写形式。
336后缀`U`和`L`使用大写形式。
337后缀`f`使用小写形式。
338表示幂的`e`和`p`使用小写形式。
339例如:
340```c
341unsigned int b = 0b0101;
342unsigned int x = 0xABCDEF;
343unsigned int u = 0U;
344long int l = 0L;
345unsigned long int ul = 0UL;
346float f = 1.0f;
347long double ld = 1.0L;
348double dd = -1.5e-5;
349double xd = 0xA.Bp12;
350```
351
352## 3.8. 注释
353使用C90风格的`/* */`,不使用C++风格的`//`。
354`/*`或`*/`与注释正文之间应有空格。行尾的注释和代码之间应有空格。
355完整语句注意首字母大写和标点符号,简单词组可以不使用标点。注意区分中英文标点。
356**TODO:**使用特定注释格式可利用doxygen等自动化工具生成文档。
357例如:
358```c
359/*
360 * This source file is part of AliOS Things.
361 * Zhang San <zhangsan@abc.com>
362 * 2021.07.01
363 */
364
365/* Zhang San <zhangsan@abc.com>
366 * 2021.07.01 */
367
368/* This pointer must NOT be NULL. */
369
370/* connecting */
371```
372
373# 4. 头文件
374## 4.1. 路径
375为避免与第三方库的头文件命名冲突,公用组件的头文件应存放于子目录中,引用时路径包含子目录名称。例如:
376```c
377#include <aos/file.h>
378```
379
380## 4.2. 引号和尖括号
381只有包含与本源文件处于同路径中的头文件时使用引号,其他情况均使用尖括号。例如:
382```c
383#include <stdio.h>
384#include "my_demo.h"
385```
386
387## 4.3. 包含次序
388包含头文件的次序如下:
389|次序   |种类|
390|-:-    |:-|
391|1      |C语言标准库头文件和工具链头文件|
392|2      |公用组件的头文件|
393|3      |本工程头文件|
394
395例如:
396```c
397#include <stdio.h>
398#include <pthread.h>
399#include <openssl/rsa.h>
400#include <aos/headers.h>
401#include "my_demo.h"
402```
403
404## 4.4. 保护
405所有头文件都应该使用`#define`保护来防止被重复包含。相关宏命名格式是`PATH_FILE_H`。
406例如,头文件*aos/common.h*可按如下方法保护:
407
408```c
409#ifndef AOS_COMMON_H
410#define AOS_COMMON_H
411
412/* 全部内容 */
413
414#endif /* AOS_COMMON_H */
415```
416
417## 4.5. 函数、变量声明
418头文件中的函数声明不使用`extern`关键字。头文件中的全局变量声明使用`extern`关键字。例如:
419```c
420void aos_cfg_file_close(int fd);
421extern char **aos_process_argv;
422```
423
424## 4.6. extern "C"关键字
425公用头文件中声明的函数和全局变量应该使用`extern "C"`关键字修饰。
426`#include`不应使用`extern "C"`关键字修饰。
427`#define`、类型定义不作要求,可酌情考虑。
428例如:
429```c
430#ifndef AOS_COMMON_H
431#define AOS_COMMON_H
432
433#include <stddef.h>
434
435#define AOS_STRING_MAX_LEN      127
436#define AOS_LSTRING_MAX_LEN     511
437
438typedef struct aos_tm {
439    unsigned int sec;
440    unsigned int min;
441    unsigned int hour;
442    unsigned int mday;
443    unsigned int mon;
444    unsigned int year;
445} aos_tm_t;
446
447#ifdef __cplusplus
448extern "C" {
449#endif
450
451extern char aos_process_symbol[AOS_STRING_MAX_LEN + 1];
452
453void aos_start(void);
454
455#ifdef __cplusplus
456}
457#endif
458
459#endif /* AOS_COMMON_H */
460```
461
462# 5. 其他注意事项
463只在本编译单元使用的函数、全局变量应使用`static`修饰符。
464在不影响功能的前提下,指针类型的函数参数尽量使用`const`修饰符。
465自增、自减运算符单独使用时采用后置形式。
466数组、结构体初始化列表、枚举类型定义中的最后一个成员之后应有逗号。例如:
467```c
468int offsets[] = {
469    0,
470    1,
471};
472```
473打印到终端的信息一律使用英文,以避免终端编码问题。
474