汕尾海湾 发表于 2022-8-12 04:05:32

专业的C头文件设计和重构指南

头文件设计要点:
1、 头文件注释
2、 guard define
3、 尽量不要在头文件中暴露数据结构
4、 要自包含,保证头文件独立编译和功能正确
5、 函数声明前加XXX_API利于拓展
6、 宏的定义
7、 对外提供的头文件放于指定的目录结构
 
1. 文件头注释
应该加在每个头文件的顶部,必须包含版权许可、功能说明、作者和创建日期。
例如:
a)   /*
b)   * Copyright (c) Huawei Technologies Co., Ltd. XXXX. All rights reserved.
c)   * Description: 文件功能描述
d)   * Author: 王二
e)   * Create: 2012-12-22
f)  */
 
2. guard define
整个头文件应该在guard define之间, 为防止头文件被多重包含,所有头文件都应当使用 #define 作为包含保护;
定义包含保护符时,应该遵守如下规则:
ü  保护符使用唯一名称;
ü  建议考虑项目源代码树顶层以下的文件路径 不要在受保护部分的前后放置代码或者注释,文件头注释除外。
假定 VOS 工程的 timer 模块的 timer.h,其目录为 vos/include/timer.h 。其保护符若使用 'TIME_H' 很容易不唯 一,所以使用项目源代码树的全路径,如:
如:
1)   #ifndef VOS_INCLUDE_TIMER_H
2)   #define VOS_INCLUDE_TIMER_H
3)    //声明对外的接口
4)    #endif
 
另外,如果这个头文件可能给c++使用,要加上
1)  #ifdef __cplusplus
2)  extern "C" {
3)  #endif
4)  //声明对外的接口
5)  #ifdef __cplusplus
6)  }
7)  #endif
注:禁止在 extern "C" 中包含头文件
 
3. 尽量不要在头文件中暴露数据结构
这样可以用户降低对你的实现的依赖,也减少了用户的编译时间
1)  typedef struct lua_State lua_State;
2)  LUA_API lua_State *lua_open (void);
3)  LUA_API void       lua_close (lua_State *L);
 
可以看到虽然用户会一直使用lua_State,但是并不知道lua_State的结构是什么
从一个使用lua的例子程序可以看出:
1)  #include "lua.h"
2)  #include "lauxlib.h"
3)  #include "lualib.h"
4)    
5)  int main(int argc, char *argv[])
6)  {
7)      lua_State *L = lua_open();
8)      const char *buf = "var = 100";
9)      int var ;
10)    luaopen_base(L);
11)    luaopen_io(L);
12)    lua_dostring(L, buf);
13)    lua_getglobal(L, "var");
14)    var = lua_tonumber(L, -1);
15)    lua_close(L);
16)    return 0;
17)}
 
4. 要自包含,保证头文件独立编译和功能正确
  i.     能独立编译
要保证本头文件在任何使用场景下,都能独立编译。其中,相对路径引入就是一个案例。如:
#include 不是标准库情况下,可能无法引入相关文件,可改成:
#include “../../XXX/XXX/ comm_type.h”
 
ii.     对外提供功能正确
头文件能独立编译了,但不一定能对外提供的功能是正确的。对外提供的功能可能还需要代码适配,以保证功能正确。
5.  下面是一个私有宏暴露给模块外部,致使相关模块使用时能编译通过,但功能却不正确。
解决办法就是不要暴露数据本身,通过接口反馈给外部模块数据值即可。(这个问题,也是尽量不要在头文件中暴露数据结构的一个典型案例)
案例:
1)  功能失败点,从图1看到rsaM.Datalen = RSA_LEN,再从图2看到RSA_LEN != pData->dataLen的应该是相等,但实际结果却是错误的,这就是问题暴露点
http://image.huawei.com/tiny-lts/v1/images/64057264565c5179be0f_554x267.png@900-0-90-f.png
图1
http://image.huawei.com/tiny-lts/v1/images/2764e264332c6aeb7cf4_554x230.png@900-0-90-f.png
图2
2)  继续追查RSA_LEN,它是RSA_KEY_LEN的重定义,见图3。而从图4可以看出RSA_KEY_LEN的值由一个开关控制,编译环境不一样,值不同
http://image.huawei.com/tiny-lts/v1/images/deea7264332c6c9b9461_422x59.png@900-0-90-f.png
图3
http://image.huawei.com/tiny-lts/v1/images/687d1264332c6db82158_554x87.png@900-0-90-f.png
图4
3)  问题在于本模块对外提供能力时,没有考虑到编译环境的要求,应用方也没有明白这点,应用方无法掌控这个宏的编译环境,从而无法得到正确值引发了这个问题。解决办法就是通过接口对外提供值,而不是数据结构本身。
http://image.huawei.com/tiny-lts/v1/images/1a3a3264332c6eeaf023_554x94.png@900-0-90-f.png
 
5. 函数声明前加XXX_API利于拓展
iii.      Lua的例子,如果定义了LUA_API就是给LUA内部使用的,如果没定义LUA_API就是for user的
下面是内部使用的例子:
1)  #ifndef LUA_API
2)  #define LUA_API
3)  #endif
4)   
5)  LUA_API lua_State *lua_open (void);
 
下面是外部使用的例子:
6)  #ifndef LUA_H
7)  #define LUA_H
8)  #endif
9)   
10)LUA_H lua_State *lua_close (void);
   
6. 宏的定义时,尽量使用括号来包住所定义的对象
  i.     宏定义嵌入某个表达式使用,用括号将其包住。此方法如果用于语句环境将会出语法错误
1)  #define LUA_TNONE       (-1)
2)   
3)  #define lua_register(L,n,f) \
4)         (lua_pushstring(L, n), \
5)          lua_pushcfunction(L, f), \
6)          lua_settable(L, LUA_GLOBALSINDEX))
 ii.     宏定义当语句使用,用do while(0)将其包住。此方法如果用于表达式环境将会出语法错误
7)  #define DOSOMETHING () \
8)         do{
9)             foo1();
10)           foo2();
11)          }while(0)
 
7. 对外提供的头文件放于指定的目录结构
一般应该使用一个单独的include目录来包含要发布的头文件,但不应该把内部使用的头文件包含进去。例如:Lua的include目录只包含了三个头文件lauxlib.h , lua.h, lualib.h,很简洁

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 专业的C头文件设计和重构指南