如何将C语言代码风格为?

摘要:本文从工程实践角度系统梳理了 C 语言中常见的命名风格与复合语句对齐及缩进风格。在命名方面,本文指出 了C 语言并不存在绝对统一的命名法,snake_case、SCREAMING_SNAKE_CASE、camelCase、PascalCas
一、C语言命名风格   C语言的命名风格是可以随便发明的,因为每个人都可以按照自己的想法去命名,所以这里没法列出所有的命名风格。但是无论你使用的是何种风格,都需要明白命名的本质不是为了“好看”,而是“防错、易读、可维护”,C语言的命名规范本质上是约束人,而不是取悦人。所以当我们提及某些工程代码命名的糟糕时,需要知道真正糟糕的不是某种命名法,而是混乱和无纪律。 (一)snake_case(蛇形命名) 1、规则   蛇形命名是全小写,又名下划线命名,同时各个单词之间要用下划线“_”分隔开,不能使用大小写混合和连字符。 2、应用场景   蛇形命名风格常见于变量名、函数名和结构体成员中,但是在使用此命名风格时需要注意单词不要太多,因为名字太长会严重影响代码的阅读体验。试想一下,一个命名就直接占用30个字符的空间,是不是想着就感觉很夸张,看着就就像是看英文句子。下面针对常用的场景给出例子: /* 变量命名 */ int buffer_len; char user_name[32]; /* 函数命名 */ void usart_init(void); int file_read(const char *file_path, char *buf); /* 结构体成员命名 */ typedef struct { int id; char display_name[32]; }usart_t; 从上面的代码示例我们可以看出,使用蛇形命名还是很直观的。 (二)SCREAMING_SNAKE_CASE(大写蛇形命名) 1、规则   SCREAMING_SNAKE_CASE通常用于预处理阶段可见的标识符,尤其是#define定义的宏,其命名规则与snake_case一致,但全部使用大写字母,如下所示: #define MAX_BUFFER_SIZE 128 #define HTTP_ERR_TIMEOUT 123 2、应用场景   大写蛇形命名常用于宏常量、条件编译开关和宏函数中,在实际使用时需要注意,该风格用于定义宏并进入公共头文件后,可能对全局预处理环境产生影响,因此通常需要加模块前缀。,此风格的常用场景示例如下: /* 宏常量 */ //未加模块前缀 #define MAX_BUFFER_SIZE 1024 #define TIMEOUT_MS 5000 /* 公共宏加模块前缀:需要注意加模块前缀最主要是为了区分宏的归属,做隔离, 因为宏有三个普通变量没有的危险属性: (1)没有作用域; (2)没有类型; (3)无法通过语言层面的作用域机制进行隔离。 */ #define HTTP_CLIENT_MAX_BUFFER_SIZE 1024 #define HTTP_CLIENT_TIMEOUT_MS 5000 /* 条件编译开关 */ #define ENABLE_DEBUG /* 宏函数 */ #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) (三)camelCase(小驼峰命名法) 1、规则   camelCase是首单词小写,后续单词首字母大写,中间不使用分隔符,具体示例如下所示: bufferSize readFile httpClientInit 2、应用场景   camelCase在C语言中常用于对外API函数和高层业务逻辑变量中,譬如: /* 对外API函数 */ int initDevice(uint32_t deviceID); size_t readData(uint8_t *buffer, size_t bufferSize); /* 高层业务逻辑变量 */ typedef enum{ STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED }ConnectionState; typedef struct{ ConnectionState connectionState; size_t packetLength; uint32_t retryLimit; }SessionContext; (四)PascalCase(大驼峰命名法) 1、规则   PascalCase又叫UpperCamelCase(帕斯卡命名法),该风格所有单词首字母大写,且单词之间不使用分隔符,具体示例如下所示: HttpClient RingBuffer DeviceConfig 2、应用场景   PascalCase常用于结构体和类型名中,该命名风格常用于表达一些“抽象概念”,在查看此风格时我们应该能感受到此风格命名后的结构体/类型名与普通变量在视觉上具备较为明显的区分,譬如: /* 结构体 */ typedef struct HttpClient{ int socket_fd; int timeout_ms; }HttpClient; /* 枚举类型名 */ typedef enum DeviceState{ DEVICE_INIT, DEVICE_READY, DEVICE_ERROR }DeviceState; (五)kebab-case(短横线命名) 1、规则   kebab-case全部单词小写,每个单词之间用“-”连接。该风格具体示例如下: home-page-styles.css 2、应用场景   kebab-case常用于文件系统层面,同时需要注意此风格不能用于C语言标识符中,譬如: /* 源文件与头文件 */ http-client.c ring-buffer.h /* 工具和脚本 */ build-tool.sh flash-firmware.sh (六)Hungarian Notation(匈牙利命名法) 1、规则   Hungarian Notation分为两种,分别是Systems Hungarian Notation(系统匈牙利命名法)和Apps Hungarian Notation(应用匈牙利命名法),其使用前缀分别表示变量类型和语义,前缀通常为一个或多个字母,在使用此风格的时候需注意变量名主体要有清晰、明确的语义。 2、应用场景   Systems Hungarian Notation通过命名前缀显式表达变量的编码类型,这种方法目前已经被淘汰了,因为使用这种方法相当于用命名重复的表达“类型信息”,而这些信息目前已经被现代语言的类型系统、编译器和工具链可靠的提供。同时,在类型演进或重构过程中,Systems Hungarian Notation还容易引入额外的维护成本,并可能造成语义上的误导。   Apps Hungarian Notation通过命名前缀表达变量所承载的语义约束,该命名思想在现代C工程中仍具有一定实际价值,比如现在在工程中比较常见的“轻量语义前缀”方法本质上可以视为Apps Hungarian Notation的弱化和演进形式。下面我会结合下示例去说明“轻量语义前缀”和Apps Hungarian Notation这两种方法的具体区别: /* 轻量语义前缀 */ int fd_socket; //fd: file descriptor;socket资源句柄。 uint8_t *buf_rx; //buf: buffer;接收用缓冲区。 size_t len_buf; //len: length;缓冲区长度。 int idx_read; //idx: index;读取位置索引。 uint32_t cnt_retry; //cnt: count;重试次数。 /* Apps Hungarian Notation */ int hSocket; //h: handle;socket句柄。 uint8_t *pbRx; //pb: pointer byte;接收字节缓冲区。 size_t cbBuf; //cb: count byte;缓冲区大小(字节)。 int iRead; //i: index;读取索引。 uint32_t cRetry; //c: count;重试计数。   从上述代码和注释我们可以看出,这两种命名方式在本质上都服务于同一个目标:通过命名提示变量的语义角色和使用约束。轻量语义前缀以更直观、低约定成本的方式表达语义,此种方式也更符合现代C工程的普遍实践。而Apps Hungarian Notation则通过高度约定化的前缀,在语义精度和一致性上更具约束力,但是此种方法也对团队共识提出了更高的要求。所以整体来看,这两者的差异主要体现在表达强度和使用成本上,而非命名思想本身。 (七)module-prefixed snake_case(模块前缀蛇形命名) 1、规则   module-prefixed snake_case常见于C工程中,用模块名作为统一前缀对全局可见符号进行约束,以降低命名冲突并强化符号归属,基本形式为<模块名>_<功能名>。该风格的关键在于前缀必须在整个模块内保持一致,避免同一模块出现多套前缀导致接口辨识困难和命名冲突。通常建议以模块为单位统一前缀策略:对外API、类型别名、全局变量、文件作用域静态符号都应遵循同一前缀体系,以保证跨文件检索与降低链接符号冲突的概率。 2、应用场景   module-prefixed snake_case主要用于C工程中需要的符号命名,常见可分为两类。第一类是模块对外暴露的公共接口,建议对函数与类型别名统一采用<模块名>_<功能名>的snake_case形式,并在类型别名上保留"_t"等后缀以强化“类型”语义。第二类是宏常量与枚举常量等全局可见符号,这类符号通常使用SCREAMING_SNAKE_CASE,同时在使用的时候建议附带模块前缀以减少全局命名空间污染。具体示例如下: /* 宏 */ #define HTTP_CLIENT_TIMEOUT_MS 5000 /* 类型 */ typedef struct http_client{ int socket_fd; int is_connected; }http_client_t; /* 公共API函数 */ http_client_t *http_client_create(const char *host, unsigned short port); void http_client_destroy(http_client_t *client); int http_client_send(http_client_t *client, const void *data, unsigned int len); (八)suffix-based naming(后缀约定命名法) 1、规则   suffix-based naming是在名称末尾额外添加语义后缀,此后缀需要项目团队统一约定,一般是以稳定、低数量、强语义为标准,同时需要注意suffix-based naming并不影响名称主体的命名风格。 2、应用场景   suffix-based naming并不是一种独立于snake_case、camelCase等风格之外的命名风格,而是一种叠加在主命名风格之上的语义标注机制,需要注意的是,后缀只能作为语义补充,不能取代清晰的主体命名或良好的接口设计。suffix-based naming常用于类型、枚举、回调函数和函数指针接口中,具体示例如下: /* 类型,_t */ typedef struct http_client{ int socket_fd; }http_client_t; /* 枚举,_t */ typedef enum http_client_state{ HTTP_CLIENT_STATE_IDLE, HTTP_CLIENT_STATE_CONNECTED }http_client_state_t; /* 回调函数,_cb */ typedef void (*http_client_event_cb)(http_client_state_t state void *user_data); /* 函数指针接口,_fn */ typedef int (*http_client_send_fn)(http_client_t *client, const void *data, unsigned int len); 二、C语言复合语句的对齐与缩进风格   花括号“{}”的位置与缩进方式本质上反映的是”人如何阅读代码“与”机器如何处理文本“之间的平衡。在早期C代码更强调紧凑与终端友好,但随着项目规模扩大、多人协作普及,逐渐演化出强调结构可视化、可维护性与工程规范的多种风格。 (一)K&R风格 1、规则   在K&R风格中左花括号“{”与控制语句位于同一行,语句块内部按照统一层级缩进(常见4空格或1个TAB,由团队统一约定),else通常与前一行右花括号在同一行书写: } else {。具体示例如下所示: if(line_num == MAX_LINES){ line_num = 0; page_num++; log_page_turn(page_num); }else{ line_num++; } 2、背景与适用场景   K&R风格源自C语言早期权威书籍《The C Programming Language》,又称“白皮书”。K&R风格是最早被广泛接收并长期延续下来的写法,因此在大量跨平台的C工程中形成了事实标准。该风格在终端环境、工具类程序以及通用库代码中非常常见,尤其适合逻辑相对集中、嵌套层级不深的代码。直到今天,K&R风格仍然是跨平台C项目中最常见的风格之一。 (二)Allman风格 1、规则   在Allman风格中左花括号“{”单独占一行,与控制语句对齐,右花括号“}”同样独立成行,强调语句块结构的垂直对应关系,该风格缩进宽度由团队统一约定。具体示例如下所示: if(line_num == MAX_LINES) { line_num = 0; page_num++; log_page_turn(page_num); } else { line_num++; } 2、背景与适用场景   Allman风格在项目规模扩大、代码审查和教学需求增强后逐渐流行,其核心目标是让花括号边界一眼可见,降低阅读和检查结构错误的成本。该风格在部分Windows工程、教学示例以及强调结构清晰性的团队中较为常见,但代码纵向展开明显,不适合对屏幕空间敏感的场景。目前Allman风格在现代C项目中属于小众但稳定存在的风格,未被淘汰,但使用范围明显收缩。 (三)GNU风格 1、规则   在GNU风格中左花括号“{”单独成行,语句块相对于控制语句再缩进一级,常见缩进宽度为2空格或其倍数。具体示例如下所示: if(line_num == MAX_LINES) { line_num = 0; page_num++; log_page_turn(page_num); } else { line_num++; } 2、背景与适用场景   GNU风格是伴随GNU工程的发展逐步形成,其设计重点是确保庞大的源码体系保持高度一致的排版规则,而非追求紧凑或美观。该风格主要存在于GNU工具链及相关C项目中,在非GNU生态中已不具备扩散性。 (四)Linux Kernel风格 1、规则   在Linux Kernel风格中花括号位置整体接近K&R,但Linux Kernel风格使用固定且较宽的缩进宽度(通常使用1个TAB,显示宽度为8列),同时倾向通过早返回来减少else和嵌套层级。需要注意Linux Kernel风格禁止混用 TAB 和空格。具体示例如下: if(line_num == MAX_LINES){ line_num = 0; page_num++; log_page_turn(page_num); return; } line_num++; 2、背景与适用场景   Linux内核在长期演化中形成了独立于GNU的编码规范,Linux Kernel风格更关注可维护性和逻辑清晰度,而非视觉上对齐的美观。该风格在内核、驱动以及底层系统软件开发中具有广泛的影响力。目前Linux Kernel风格在系统级C领域中高度活跃且影响力还在持续扩大。 (五)BSD / KNF风格 1、规则   在BSD / KNF风格中花括号写法于K&R类似,语句块缩进强调TAB和列对齐的一致性(缩进宽度通常是1个TAB,显示宽度为8列)。具体示例如下: if(line_num == MAX_LINES){ line_num = 0; page_num++; }else{ line_num++; } 2、背景与适用场景   BSD / KNF风格源于BSD Unix体系,是传统Unix C工程文化的延续。该风格在BSD系统和大量Unix工具源码中广泛存在,强调长期维护下的稳定可读性。目前BSD / KNF风格在现代C中属于低曝光但长期稳定存在的风格。 (六)Whitesmith风格 1、规则   在Whitesmith风格中左花括号“{”单独成行,且花括号与整个语句块整体右移一级缩进(缩进宽度通常是1个TAB或4个空格),同时语句块内容并不与控制语句对齐。具体示例如下: if(line_num == MAX_LINES) { line_num = 0; page_num++; log_page_turn(page_num); } else { line_num++; } 2、背景与适用场景   Whitesmith风格源自早期商业 C 编译器,其设计初衷是通过额外缩进强化语句块的从属关系。随着代码规模和嵌套层级增加,该风格在可读性上的劣势逐渐显现,目前主要用于理解和维护遗留代码,而不适合新项目采用。目前Whitesmith风格在现代C中基本已经被淘汰。 (七)MISRA-C视角下的花括号使用 1、规则   所有控制语句必须显式使用花括号,即使语句块仅包含一条语句。具体示例如下: if(flag){ do_something(); } 2、背景与适用场景   MISRA-C 面向安全关键系统制定,其对花括号的强制要求并非风格偏好,而是为了防止维护阶段引入隐式逻辑错误。该约束在工业控制、汽车电子等高可靠性 C 项目中具有明确的工程意义,通常高于个人或团队的排版习惯。目前MISRA-C对花括号的要求在一些特定领域呈现强化趋势。