curl C 代码风格指南:从规范到 checksrc 自动化检查的完整实战
2026/9/11 15:24:14 网站建设 项目流程

curl C 代码风格指南:从规范到 checksrc 自动化检查的完整实战

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

curl 是一个拥有数十年历史的开源项目,其 C 代码库横跨 libcurl 库、命令行工具与测试框架,长期由大量贡献者协作维护。为了让代码读起来像"同一套代码",项目在 docs/internals/CODE_STYLE.md 中固化了一套严格的 C89 代码风格规范,并通过 scripts/checksrc.pl 脚本在构建时自动执行检查。本文以该风格文档为骨架,完整梳理 curl 的代码风格规则,并结合仓库源码与构建系统说明其落地方式,帮助贡献者在提交代码前写出风格合规、零警告的 C 代码。

为什么 curl 需要一套统一的代码风格

统一风格的价值远不止"好看"。代码风格一致能显著降低阅读与维护成本:

  • 可读性是第一特性:代码的意图必须对读者可见,清晰无歧义比"聪明"地省两行代码更重要。curl 项目强调,写简单代码,让未来十年里回来调试的人能快速看懂。
  • 降低评审与调试成本:新代码评审、Bug 定位时,统一的格式让 diff 更干净、问题更容易暴露。
  • 统一胜过个人偏好:项目明确表态,"统一风格比满足个别贡献者的个人品味更重要"。

这些规则中大部分由 scripts/checksrc.pl 脚本自动校验,可通过make checksrc手动运行,也可在./configure --enable-debug之后由构建系统在编译时默认执行。此外,curl 还要求代码在尽可能多的主流平台上零编译警告,产生警告的代码不会被原样接受。

命名规范:清晰、小写、静态化

  • 新函数与变量命名应逻辑清晰、按用途命名,不必强求与其他位置一致,但要可理解。
  • 文件内部的函数必须声明为static
  • 项目偏好小写命名

对于库内导出的、非全局可见的符号,curl 有专门的命名约定(见 docs/internals/README.md 中关于内部文档的说明)。从源码结构看,libcurl 内部大量函数以Curl_前缀标识内部全局符号,例如登录信息解析函数Curl_parse_login_details()在 lib/url.h 声明、在 lib/url.c 实现,并被 lib/setopt.c 与 lib/urlapi.c 多处调用。这种前缀约定让库内符号与公开 API 一目了然地区分开来。

排版基础:缩进、注释与行长

只用空格缩进,两级一档

curl 代码禁止使用 TAB,每级缩进为两个空格

if(something_is_true) { while(second_statement == fine) { moo(); } }

注释只能用/* ... */

curl 坚持编写C89 代码,而//行注释直到 C99 才进入标准,因此一律禁止//注释,只能使用/* comment */形式:

/* this is a comment */

行长不得超过 79 列

源码宽度永远不允许超过 79 列,即使在宽屏时代依然如此,原因有二:

  1. 窄列比宽列更易读——报纸分栏排版沿用数百年的道理;
  2. 窄列允许开发者在同一屏幕上并排打开两三个源码窗口、多个终端与调试窗口。

对应地,checksrcLONGLINE警告会检测超过 79 列的行,这一上限在 scripts/checksrc.pl 中定义为my $max_column = 79;

控制流与花括号:位置决定可读性

if/while/do/for 的花括号

if/while/do/for表达式中,开括号{与关键字同行,闭括号与关键字同缩进级别对齐:

if(age < 40) { /* clearly a youngster */ }

若块内只有一条单行语句,可省略花括号

if(!x) continue;

函数的花括号单独占一行

与普通控制流不同,函数的开括号应独占一行

int main(int argc, char *argv[]) { return 1; }

else 必须另起一行

带花括号的else子句要写在闭括号之后的新行上:

if(age < 40) { /* clearly a youngster */ } else { /* probably grumpy */ }

这与许多项目"} else {"的写法不同,checksrcBRACEELSE警告专门检查这一规则。

关键字与左括号之间不留空格

if/while/do/for与左括号之间不得有空格

while(1) { /* loop forever */ }

对应警告为SPACEBEFOREPAREN(检测if (这类写法)。

条件用布尔语义表达

if/while条件中,不要显式与TRUE/FALSENULL/!= NULL0/!= 0比较,直接使用布尔语义:

result = do_something(); if(!result) { /* something went wrong */ return result; }

checksrcEQUALSNULLNOTEQUALSZERO警告分别拦截== NULL!= 0的写法。

禁止在条件内赋值

为提高条件表达式的可读性、降低复杂度,禁止在 if/while 条件中赋值。这种写法被明确反对:

if((ptr = malloc(100)) == NULL) return NULL;

应拆开书写:

ptr = malloc(100); if(!ptr) return NULL;

checksrcASSIGNWITHINCONDITION警告对应此规则。

新块必须换行,禁止单行多条语句

同一源码行内绝不写多条语句,即使是很短的if()条件:

if(a) return TRUE; else if(b) return FALSE;

绝不允许:

if(a) return TRUE; else if(b) return FALSE;

对应警告为ONELINECONDITION

表达式与运算符:留白有讲究

运算符两侧留空格

C 表达式中,运算符两侧都应留空格。后缀运算符()[]->.++--一元运算符+-!~&除外——它们与操作数之间不得有空格:

bla = func(); who = name[0]; age += 1; true = !false; size += -2 + 3 * (a + b); ptr->member = a++; struct.field = b--; ptr = &address; contents = *pointer; complement = ~bits; empty = (!*string) ? TRUE : FALSE;

checksrcEQUALSNOSPACENOSPACEEQUALSMULTISPACEEXCLAMATIONSPACE等警告都在守护这些细节。

类型转换与表达式"贴紧"

curl 尽量回避类型转换;不得不使用时,类型转换与后面的表达式之间不留空格

int value = (int)foobar; char *ptr = (char *)random_func();

return 不加括号,sizeof 必须加括号

return语句的值不加多余括号

int works(void) { return TRUE; }

sizeof必须带括号

int size = sizeof(int);

checksrcRETURNNOSPACESIZEOFNOPAREN分别检测这两种情况。

列对齐:长表达式的续行规范

当语句因过长、难读或风格限制必须跨多行时:

  • 表达式或子表达式的续行应与所属列对齐,方便看出它是语句的哪一部分;
  • 续行不能以运算符开头
  • 其他情况遵循两级空格缩进。

文档中给出 libcurl 真实代码的四种典型续行模式:

1. 括号表达式按括号内侧对齐

if(Curl_pipeline_wanted(handle->multi, CURLPIPE_HTTP1) && (handle->set.httpversion != CURL_HTTP_VERSION_1_0) && (handle->set.httpreq == HTTPREQ_GET || handle->set.httpreq == HTTPREQ_HEAD)) /* did not ask for HTTP/1.0 and a GET or HEAD */ return TRUE;

2. 无括号时使用默认缩进

data->set.http_disable_hostname_check_before_authentication = va_arg(param, long) ? TRUE : FALSE;

3. 函数调用以开括号为对齐基准

if(option) { result = parse_login_details(option, strlen(option), (userp ? &user : NULL), (passwdp ? &passwd : NULL), NULL); }

这个示例中的parse_login_details在仓库中对应的真实函数是Curl_parse_login_details(),其参数表(loginlenuserppasswdpoptionsp)在 lib/url.c 有完整注释:它用于解析useruser:passworduser:password;options等登录字符串格式,是 libcurl 处理 URL 与 setopt 登录参数的核心工具。

4. 与当前"打开的"括号对齐

DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing " "server response left\n", (int)clipamount));

对应警告为INDENTATION(注意它只检查特定位置,难免漏检)。

平台相关代码:用 HAVE_FEATURE 而非平台判断

平台相关代码应使用#ifdef HAVE_FEATURE做条件编译,避免在#ifdef中直接判断特定操作系统或硬件HAVE_FEATURE宏在类 Unix 系统上由 configure 脚本生成,在其他系统上则硬编码在config-[system].h文件中(如 lib/config-win32.h、lib/config-mac.h 等)。

同时鼓励使用在功能未编译时可退化为空或常量的宏/函数,让代码在不同构建配置间无缝衔接。例如magic()依据编译期条件产生不同行为:

#ifdef HAVE_MAGIC void magic(int a) { return a + 2; } #else #define magic(x) 1 #endif int content = magic(3);

结构体:用 struct name,禁用 typedef

可以使用结构体,但不要为其 typedef,统一用struct name方式标识:

struct something { void *valid; size_t way_to_write; }; struct something instance;

不允许

typedef struct { void *wrong; size_t way_to_write; } something; something instance;

对应警告为TYPEDEFSTRUCT

禁用函数清单:从源头避免"脚枪"

为避免踩坑与意外后果,curl禁止使用一批 C 函数checksrc脚本发现使用会直接报错(BANNEDFUNC警告)。在 scripts/checksrc.pl 中,这些函数以%banfunc哈希表形式硬编码,与文档中的清单完全一致。完整清单如下:

_access _fstati64 _lseeki64 _mbscat _mbsncat _open _tcscat _tcsdup _tcsncat _tcsncpy _waccess _wcscat _wcsdup _wcsncat _wfopen _wfreopen _wopen abort accept accept4 access aprintf assert atoi atol calloc close CreateFile CreateFileA CreateFileW fclose fdopen fopen fprintf free freeaddrinfo freopen fstat getaddrinfo gets gmtime inet_ntop inet_pton llseek LoadLibrary LoadLibraryA LoadLibraryEx LoadLibraryExA LoadLibraryExW LoadLibraryW localtime lseek malloc mbstowcs MoveFileEx MoveFileExA MoveFileExW msnprintf mvsnprintf open printf realloc recv rename send snprintf socket socketpair sprintf sscanf stat strcat strcpy strdup strerror strncat strncpy strtok strtok_r strtol strtoul vaprintf vfprintf vprintf vsnprintf vsprintf wcscpy wcsdup wcsncpy wcstombs WSASocket WSASocketA WSASocketW

这些函数大多有安全替代品:例如内存分配与释放统一走 curl 内部的curl_malloc/curl_free等包装与curlx_safefree()安全释放(USESAFEFREE警告鼓励用curlx_safefree(var)取代"curlx_free(var)后紧跟赋 NULL"的两步写法);snprintf被替换为返回码语义不同的内部版本curl_msnprintfSNPRINTF警告);strerrorstderr也在扩展警告中被禁止。

checksrc:让风格检查自动化

检查的触发方式

  • 手动运行:仓库根目录执行make checksrc,该目标在顶层 Makefile.am 中定义,会依次进入libsrctestsinclude/curldocs/examplesprojects等目录执行检查。
  • 构建时自动运行:使用./configure --enable-debug配置后,构建系统会在编译时默认执行 checksrc(见 lib/Makefile.am 中的checksrc:目标,它通过@PERL@ $(top_srcdir)/scripts/checksrc.pl -D$(srcdir) $(CSOURCES) $(HHEADERS)对全部源文件与头文件执行扫描)。

命令行用法

checksrc.pl [options] [file1] [file2] ...
  • -W[file]:跳过指定文件,不检查(常用于生成的文件);
  • -D[dir]:访问文件时前置的目录名;
  • -h:显示帮助,同时列出所有可识别的警告。

checksrc 检查什么

checksrc并不校验全部风格规则,而是着力于捕获贡献者最常见的格式与语法错误。其全部告警的权威清单见 docs/internals/CHECKSRC.md,与本文各节规则一一对应,例如:

警告含义对应风格规则
ASSIGNWITHINCONDITION条件表达式内赋值禁止在条件内赋值
ASTERISKNOSPACE/ASTERISKSPACE指针声明char* name/char * name星号应紧贴变量名
BANNEDFUNC使用了禁用函数禁用函数清单
BRACEELSE} else同行else 另起一行
BRACEPOS开括号位置错误花括号位置
COMMANOSPACE逗号后无空格运算符空格
CPPCOMMENTS出现//注释只用/* */
EQUALSNULL使用== NULL比较!var
FOPENMODEcurlx_fopen()模式串未用宏平台相关代码
LONGLINE行宽超过 79 列行长限制
NOTEQUALSZERO使用!= 0if(var)
ONELINECONDITIONif()与块同行新块换行
SIZEOFNOPARENsizeof未加括号sizeof 加括号
SNPRINTF使用了snprintf()curl_msnprintf()
SPACEBEFOREPAREN关键字后、括号前有空格括号前无空格
TABS出现 TAB 字符只用空格缩进
TRAILINGSPACE行尾空白排版整洁
TYPEDEFSTRUCTtypedef 结构体禁用 typedef struct
UNUSEDIGNORE内联忽略指令未被使用忽略指令应被用上
USESAFEFREEcurlx_free()后跟赋 NULLcurlx_safefree()

扩展警告:按目录启用

部分警告计算开销较大,默认关闭。可在需要启用的目录下放置.checksrc文件,每行一个指令启用,格式为:

enable <EXTENDEDWARNING>

目前可启用的扩展警告包括:

  • COPYRIGHTYEAR:当前改动未更新源文件中的版权年份;
  • STRERROR:使用了禁用函数strerror()
  • STDERR:使用了禁用变量stderr

.checksrc的解析逻辑(包括enable/disable指令处理)可在 scripts/checksrc.pl 中看到。

如何豁免个别警告

由于源码特性和工具本身的局限,有时需要豁免特定警告,checksrc提供两种途径:

1. 内联忽略(推荐):在源码文件内通过注释指令控制,仅对该文件生效。忽略某警告直到重新启用:

/* !checksrc! disable LONGLINE all */

之后用以下指令重新启用(文件结束前未启用则下一个文件自动恢复):

/* !checksrc! enable LONGLINE */

也可以只忽略 N 次违规,精确控制豁免范围(例如某个确实无法缩短且被认可的长行):

/* !checksrc! disable LONGLINE 1 */

次数用完自动重新启用,确保只忽略预期的实例。若写了忽略指令却没用上,UNUSEDIGNORE会提示移除或修正。

2. 目录级跳过文件(已弃用):在出现误报的源码目录下创建checksrc.skip文件,把完整违规行写入其中。这是旧方法,项目已转向尽量使用内联忽略。

实战要点:让新代码一次通过

综合风格文档与 checksrc 工具,贡献者提交前可快速自查:

  1. 编辑器先行:将编辑器设置为空格缩进(每级 2 空格)、禁止 TAB、显示 79 列标尺,从源头避免TABSLONGLINEINDENTATION
  2. 用 C89 心智写代码:注释只用/* */,变量声明遵循 C89 约束;
  3. 遵循控制流模板if/else、循环、函数的花括号位置与换行方式直接参考上文模板,else单独成行;
  4. 善用布尔条件if(!ptr)if(result),拒绝== NULL!= 0、条件内赋值;
  5. 内存与字符串安全:绕开禁用函数清单,使用 curl 内部的curl_msnprintfcurlx_safefree等替代品;
  6. 提交前跑一遍:执行make checksrc,或基于--enable-debug构建让检查自动生效;遇到确需豁免的个别情况,用/* !checksrc! disable <WARN> N */精确豁免并确保其被真正使用。

将以上规则内化为习惯后,写出的代码不仅风格统一、易于评审,还能与 curl 整个代码库无缝融合——这正是这份风格指南与 checksrc 工具存在的意义:让数十年的协作代码始终像出自同一人之手。

延伸阅读:风格检查工具的完整告警清单见 docs/internals/CHECKSRC.md;检查脚本实现见 scripts/checksrc.pl;风格文档原文见 docs/internals/CODE_STYLE.md。

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询