1 extern "C" 里不要放 #include
项目中, 常见类似如下 头文件
#ifndef __MY_HANDLE_H__
#define __MY_HANDLE_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <typedef.h>
#include <errcode.h>
typedef void* my_handle_t;
typedef unsigned int result_t;
my_handle_t create_handle(const char* name);
result_t operate_on_handle(my_handle_t handle);
void close_handle(my_handle_t handle);
#ifdef __cplusplus
}
#endif
#endif /* __MY_HANDLE_H__ */
`这似乎没问题`, 前提是 `该头文件 从来没被 C++ 程序 引用过`
`预定义宏__cplusplus:`
由 C++ 规范规定
`C++ / C compiler 预定义/不预定义 它`
值通常为 199711L
(1) 被 C / C++ 程序 引用 时, 分别 等价于
// 被 C code 引用
#ifndef __MY_HANDLE_H__
#define __MY_HANDLE_H__
#include <typedef.h>
#include <errcode.h>
typedef void* my_handle_t;
typedef unsigned int result_t;
my_handle_t create_handle(const char* name);
result_t operate_on_handle(my_handle_t handle);
void close_handle(my_handle_t handle);
#endif /* __MY_HANDLE_H__ */
// 被 C++ code 引用
#ifndef __MY_HANDLE_H__
#define __MY_HANDLE_H__
extern "C" {
#include <typedef.h>
#include <errcode.h>
typedef void* my_handle_t;
typedef unsigned int result_t;
my_handle_t create_handle(const char* name);
result_t operate_on_handle(my_handle_t handle);
void close_handle(my_handle_t handle);
}
#endif /* __MY_HANDLE_H__ */
1.1 extern "C" 的 前世今生
C++ compiler 名字粉碎: 编译时
, 把 C++ 源文件
中 外部可见 ( global ) name 粉碎
成 全局唯一
, 存到 二进制目标文件
的 symbol table
, 以使 linker 能区分
原本 同名 的 symbol
1. why C++ / C compiler 需要 / 不需要 名字粉碎 ?
C++ 有 namespace + function overloading
(1) C++
1) 允许 `同 name 不同 definition, 只要 语义上 无二义性: 即
`允许 不同 namespace + function overloading ( 同 name )`
2) `继承了 C 的 编译 & 链接 机制, compiler 和 linker 完全独立`
`compiler:` view every `源文件` as 独立的 `编译单元`;
经 `语义分析` 可区分 `同名 symbol`
`linker:` 只能由 `symbol table` 中 `name 识别 global variable / func`,
`link` them 生成 `可执行程序`
(2) C: 单一 namespace & 不允许 function overloading
1个 `可执行文件` 的 `所有 目标文件` 中,
`不存在 同名 symbol `, 无论是否用 `static` 修饰
C compiler 可能 仅仅对 name 进行简单 `一致` 的修饰,
如 在名字前 `统一加 下划线`
2. C / C++ 混合 编译 & 链接
(1) compile
1.c ( implement ) -> compile -> 1.o
#include "1.h" ( interface )
2.cpp ( call ) -> compile -> 2.o
#include "1.h"
#include "2.h"
(2) link
Problem: 1.o + 2.o -> link -> 2 个 .o 对 同一 global func naming 不同 -> "symbol undefined" error
(3)
solution: extern "C" ( 链接规范: C++ 引入, 告诉 C++ compiler, 按 C 语言 compile ) + __cplusplus ( C compiler 不认识 extern "C" => 预定义宏 __cplusplus 用于 区分 C++ compiler 与 C compiler, 与 extern "C" 结合使用 )
3 steps:
1) 1.h #include 之外的 content wrapped in
extern "C" { } // 修饰 declaration/definition
2) 重编 2.cpp
2.o 与 1.o symbol 一致
3) 重编 1.c
C compiler 不认识 extern "C": 报 "语法错误"
__cplusplus 区分 C 和 C++ compiler
extern "C" { 与 } 均 wrapped in #ifdef __cplusplus #endif
`1.1 节 后续 不用记, 理解即可`
// 1) my_handle.h
#ifndef __MY_HANDLE_H__
#define __MY_HANDLE_H__
typedef void* my_handle_t;
typedef unsigned int result_t;
my_handle_t create_handle(const char* name);
result_t operate_on_handle(my_handle_t handle);
void close_handle(my_handle_t handle);
#endif /* __MY_HANDLE_H__ */
// 2) my_handle.c : C 源程序
#include "my_handle.h"
my_handle_t
create_handle(const char* name)
{
return (my_handle_t)0;
}
resul_t
operate_on_handle(my_handle_t handle)
{
return 0;
}
void
close_handle(my_handle_t handle)
{
}
(1) C compiler 编译 my_handle.c
-> my_handle.o -> symbol table:
0000001a T _close_handle
00000000 T _create_handle
0000000d T _operate_on_handle
// 3) my_handle_cliient.cpp : C++ 源程序
#include "my_handle.h"
#include "my_handle_client.h" // 略
void
my_handle_client::do_something(const char* name)
{
my_handle_t handle = create_handle(name);
(void) operate_on_handle(handle);
close_handle(handle);
}
(2) C++ compiler 编译 my_handle_client.cpp
-> my_handle_client.o -> symbol table:
0000002c s EH_frame1
U __Z12close_handlePv
U __Z13create_handlePKc
U __Z17operate_on_handlePv
00000000 T __ZN16my_handle_client12do_somethingEPKc
00000048 S __ZN16my_handle_client12do_somethingEPKc.eh
(3) `link my_handle.o 和 my_handle_client.o`
=> `两个 .o 文件 对 同一 对象 / func naming 不同`
=> `linker 报 "symbol undefined" error`
Undefined symbols:
"close_handle(void*)", referenced from:
my_handle_client::do_something(char const*)in
my_handle_client.o
"create_handle(char const*)", referenced from:
my_handle_client::do_something(char const*)in
my_handle_client.o
"operate_on_handle(void*)", referenced from:
my_handle_client::do_something(char const*)in
my_handle_client.o
3. solution ( 3 steps ) : C++ 引入 `链接规范 / linkage specification`
表示为
extern "language string"
C++ compiler 支持
extern "C/C++"
对应 C/C++ 语言
`以 告诉 C++ compiler`, 对于用 `链接规范` 修饰的 `declaration/definition`,
应 `按 指定语言的方式( name, 调用习惯 等) 处理`
// 2种用法:
//1) 单个声明:
extern "C" void foo();
//2) 一组声明:
extern "C"
{
void foo();
int bar();
}
(1) my_handle.h #include 之外 wrapped in
extern "C" { }
// 1) my_handle.h
#ifndef __MY_HANDLE_H__
#define __MY_HANDLE_H__
extern "C" {
typedef void* my_handle_t;
typedef unsigned int result_t;
my_handle_t create_handle(const char* name);
result_t operate_on_handle(my_handle_t handle);
void close_handle(my_handle_t handle);
}
#endif /* __MY_HANDLE_H__ */
(2) 重编 my_handle_client.cpp
-> my_handle_client.o -> symbol table
// 2)
00000000 T __ZN16my_handle_client12do_somethingEPKc
00000048 S __ZN16my_handle_client12do_somethingEPKc.eh
U _close_handle
U _create_handle
U _operate_on_handle
->
与 C compiler 生成的 my_handle.o 符号 一致
->
relink 2个 .o, 不再报 symbol undefined
(3) 重编 my_handle.c : C compiler 不认识 extern "C"
-> 报 "语法错误"
-> 用 `宏 __cplusplus 区分 C 和 C++ compiler:`
extern "C" { 与 } 均 wrapped in #ifdef __cplusplus #endif
// 3) my_handle.h
#ifndef __MY_HANDLE_H__
#define __MY_HANDLE_H__
#ifdef __cplusplus
extern "C" {
#endif
typedef void* my_handle_t;
typedef unsigned int result_t;
my_handle_t create_handle(const char* name);
result operate_on_handle(my_handle_t handle);
void close_handle(my_handle_t handle);
#ifdef __cplusplus
}
#endif
#endif /* __MY_HANDLE_H__ */
1.2 小心门后 的 未知世界
回到 本来话题
1. why 不能把 #include 放 extern "C"{ } 里?
// eg1
1) 3 个 .h 依次被 #include 于后者
c.cpp
#include "c.h"
#include "b.h"
#include "a.h"
2) 3 个 .h 依次用 __cplusplus + extern"C" 包裹 前者
a/b/c.h
__cplusplus + extern "C" { / } wrap
include "null/a/b.h" + void a/b/c();
#include 放 extern "C" { } 内 的 2 大风险:
(1) extern "C" { } 嵌套
嵌套 可能 毫无意义;
嵌套过深 时, 如微软 compiler MSVC 可能 报错
不要因此而责备微软, 这种嵌套毫无意
(2) 可能无意中 改变 func 声明 的 链接规范
// eg3
改 eg1 中 a.h:
ifdef __cplusplus ( 无 extern "C", 本意 等价于 extern "C++")
foo() 声明
a() 声明 被 foo() 用 #define 宏替换
#else
void a(int);
#endif
// C++ 预处理器 展开 b.h:
extern "C"{
void foo(int);
用 foo(int) 宏替换的 a(int)
void b();
}
按 a.h 本意, foo 链接规范 为 "C++"
但 b.h 中, 因 #include "a.h" 放 extern"C" { } 内,
foo 链接规范 被改为 "C"
`solution: #include 放 extern"C" { } 外`
2. 预处理: 以 # 开头
#include 头文件 内容替换
#define 宏替换
#ifdef / #ifndef / #endif
1.2 节 后续不用记, 理解就行
`eg1: 嵌套过深`
// a.h
#ifndef __A_H__
#define __A_H__
#ifdef __cplusplus
extern "C" {
#endif
void a();
#ifdef __cplusplus
}
#endif
#endif
// b.h
#ifndef __B_H__
#define __B_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "a.h" // include 放 extern "C" 里
void b();
#ifdef __cplusplus
}
#endif
#endif
//c.h
#ifndef __C_H__
#define __C_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "b.h"
void c();
#ifdef __cplusplus
}
#endif
#endif
// c.cpp
#include "c.h"
`C++ compiler 预处理选项 编译 c.cpp`
extern "C" {
extern "C" {
extern "C" {
void a();
}
void b();
}
void c();
}
`eg2: 嵌套过深 -> 以 最内层嵌套 为准`
extern "C" {
extern "C++" {
void foo();
}
void bar();
}
// #include 放 extern "C" { } 外
extern "C" {
void a();
}
extern "C" {
void b();
}
extern "C" {
void c();
}
`eg3: 无意中 改变 func 声明 的 链接规范`
// a.h
#ifndef __A_H__
#define __A_H__
#ifdef __cplusplus
void foo(int);
#define a(value) foo(value)
#else
void a(int);
#endif
#endif /* __A_H__ */
// b.h
#ifndef __B_H__
#define __B_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "a.h"
void b();
#ifdef __cplusplus
}
#endif
#endif /* __B_H__ */
每条 #include 指令 后都隐藏着 一个未知的世界,
除非你刻意去探索, 否则永远都不知道,
把 #include 放在 extern "C" { } 里面时,
会有怎样的风险
或许你会说,
“我刻意去 查这些被包含的头文件,以保证它们不会带来麻烦”
但, 何必呢?
毕竟, `我们 完全可以不必为 不必要的事情买单`
1.3 应对 遗留系统
1. .cpp #include 的 .h 含 C func/var 声明,
却 没用 extern "C", 该怎么办?
(1) 邪恶方案: .cpp 中加 extern "C"
(2) 正确方案
solution1:
当 bug 发 缺陷报告 给相应 团队 / 第三方公司
solution2:
`上策是 将其视为一个 可以整理到 干净 / 合理 状态的 良好机会`
1.3 节 后续不用记, 理解就行
(1) 邪恶方案: .cpp 中加 extern "C"`
extern "C"
{
#include "a.h"
}
`背后隐含原因: 我们不能修改 a.h`
原因可能有2个:
`1) .h 属于其他团队 或 第三方公司, 你 没权修改`
`2) 有权修改`, 但 .h 属于 `遗留系统, 冒然修改 可能会带来 不可预知的问题`
(2) 正确方案
对 原因 1:
`solution1: 当 bug, 发 缺陷报告 给相应 团队 / 第三方公司`
若 .h 属于 `免费开源软件,
自己修改, 并 发布 patch 给开发团队`
对 原因 2:
若 .h 混乱而复杂, 虽然对于 遗留系统 的哲学应该是
"在它 还没有带来麻烦之前 不要动它",
但现在麻烦已经来了, 逃避不如正视. 所以,
`solution2: 上策是 将其视为一个 可以整理到 干净 / 合理 状态的 良好机会`
1.4 应对 可移植性
1. 可应对 部分 compiler 实现:
C/C++ compiler 都预定义了 _cplusplus,
且 用 0/非0 就能区分出 C/C++编译器
#ifdef __cplusplus // C/C++ compiler
#if __cplusplus // __cplusplus != 0 -> C++ compiler
extern "C" { // 想按 C 文件编译 => 非 C compiler
#endif
#endif
void foo();
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif
2. 试图 兼容各种 compiler`
难: 遇到再探讨
`(1) 用 _ALIEN_C_LINKAGE_ 标识` 在
`C 和 C++ 编译` 中 `都定义了 宏 _cplusplus 的 compiler`
__cplusplus // C / C++ / other compiler
non-__ALIEN_C_LINKAGE__ // other compiler
__ALIEN_C_LINKAGE__ + __cplusplus != 0 // C++ compiler
#ifdef __cplusplus
#if !defined(__ALIEN_C_LINKAGE__) || \
( defined(__ALIEN_C_LINKAGE__) && __cplusplus )
extern "C" { // 想按 C 编译
#endif
#endif
// Here is your declarations
#ifdef __cplusplus
#if !defined(__ALIEN_C_LINKAGE__) || \
( defined(__ALIEN_C_LINKAGE__) && __cplusplus )
}
#endif
#endif
这 违反了 DRY ( Don't Repeat Yourself ) 原则
(2) 用 `头文件`, 如 clinkage.h,
`让系统中 other 头文件 都用 宏 __cplusplus + __AN_ALIEN_IMPL__ `
// clinkage.h
// `__AN_ALIEN_IMPL__`
// 1) 没 定义: 非 C 编译
// 2) 定义 且 __cplusplus != 0: 非 C 编译
#if defined(__cplusplus) && ( \
! defined(__AN_ALIEN_IMPL__) || \
( defined(__AN_ALIEN_IMPL__) && __cplusplus ) \
)
#define __BEGIN_C_DECLS extern "C" {
#define __END_C_DECLS }
#else
#define __BEGIN_C_DECLS
#define __END_C_DECLS
#endif
#include "clinkage.h"
__BEGIN_C_DECLS
void foo();
__END_C_DECLS
1.5 extern "C" 内 不能放 #include, 那可以放什么?
链接规范 仅用于修饰 `函数 / 变量 / 函数类型`
=> 严格讲, extern "C" 内 只应该 放这 `3种对象`
但, 也可放
非函数类型定义 (`结构体 / 枚举 / 宏定义 预处理指令` 等)
1.6 尽量用 extern "C"
1. 大多数情况下, 你无法判断 你的 `头文件`
未来 是否让 C++ 代码 使用
所以, 现在就 `加上 extern "C"`,
以避免未来可能 要花更高的 成本来 定位/修复错误
2. 源代码 是否要用 extern "C" ?
extern "C" 是一个 C++ 语言元素
(1) 不用 C++ compiler 编译 C 源代码 时,
在 C 源代码 中 用 extern "C" 没什么意义
(2) 有些 `单元测试` 用 `基于 C++ 的 xUnit 框架`,
把 `被测 C源代码 文件 #include 到 测试用例文件`
=> 必须把 `C源代码中 函数定义用 extern "C" 包含起来`
1.7 特例 for #include inside extern "C"
`#pragma pack: .h 中 无 C function declaration / variable definition`
=> `链接规范 不会对 .h 内容 产生影响`
2 尽量避免 用 extern
2.1 extern + global variable 声明
对 variable, extern 只能 修饰 global variable
作用:
为 当前 编译单元
引入 定义在 其他编译单元
的 global variable 的 declaration
extern int global_var_in_another_c_file;
1. 去掉 extern, 就变成了 变量 `试探性定义:`
若在其之前 `没有`一个 `同类型、非 static 的 同名定义`,
则 为 变量 `定义`;
否则, 为 引用之前定义的一处 `声明`
extern int n; // 外部声明 外部链接
int b = 1; // 外部定义 外部链接
int f(void) { // 外部定义 外部链接
int a = 1;
return b;
}
static const char *c = "abc"; // 外部定义 内部链接
static void x(void) { // 外部定义 内部链接
}
int i1 = 1; // 定义 外部链接
int i1; // 试探性定义: 因 i1 在这之前 已定义 => 是 声明
extern int i1; // 声明 引用 前面的定义
2. 从 设计角度
讲, 横跨 多个编译单元 的 global variable
是糟糕的选择, 会造成 不必要 模块间耦合
3. 通过 设计手法 ( 用一个 func 读 global variable / sec3 中 static 等 )
可 消除 global variable
, 或 将其 控制在 1 个 编译单元内部 / 消除对 global variable 的 extern
(1) extern + global variable
//eg1. extern + global variable
//1) Implement.c
int Implement(int var1, int var2)
{
if( var2 != 0 )
return( var1 / var2 );
return 0;
}
//2) Implement.h
#ifndef _IMPLEMENT_H_
#define _IMPLEMENT_H_
int Implement(int var1, int var2);
#endif
//3) Interface.c
int gResult; // 定义 全局变量
void Interface(int var1, int var2)
{
gResult = Implement(var1, var2);
}
//4) Interface.h : extern + global variable
#ifndef _INTERFACE_H_
#define _INTERFACE_H_
#include "Implement.h"
void Interface(int var1, int var2);
extern int gResult;
#endif
//5. App.h
#ifndef _APP_H_
#define _APP_H_
#include "Interface.h"
#endif
//6. App.c
#include "App.h"
int main()
{
// Interface hides/exposes its implement/interface to App
int var1 = 10;
int var2 = 2;
Interface(var1, var2);
printf("%d\n", 10 * gResult);
}
(2) 不用 extern + global variable, 用 func 读 global variable
//1. Implement.c
int Implement(int var1, int var2)
{
if( var2 != 0 )
return( var1 / var2 );
return 0;
}
//2. Implement.h
#ifndef _IMPLEMENT_H_
#define _IMPLEMENT_H_
int Implement(int var1, int var2);
#endif
//3. Interface.c
int gResult;
void Interface(int var1, int var2)
{
gResult = Implement(var1, var2);
}
int getValue()
{
return (10 * gResult);
}
//4. Interface.h : 用 extern + global variable
#ifndef _INTERFACE_H_
#define _INTERFACE_H_
#include "Implement.h"
void Interface(int var1, int var2);
int getValue();
#endif
//5. App.h
#ifndef _APP_H_
#define _APP_H_
#include "Interface.h"
#endif
//6. App.c
#include "App.h"
int main()
{
int var1 = 10;
int var2 = 2;
Interface(var1, var2);
printf("%d\n", getValue(););
}
2.2 extern + global variable 定义/初始化: extern 多余 并 引起 compiler 警告
extern int global_var = 10;
变量 一旦 初始化, 就被 compiler 视为定义
2.3 extern + 函数声明
用意/结果:
与 extern + global variable 相同:
为 `当前 编译单元` 引入 `外部 func declaration`
1. extern 某个 func declaration
(1) 3 reason:
1) 你 `懒得找` 头文件
2) 头文件 `没声明` 该 func
头文件 设计原则:
对方 头文件 中 没声明 的 func
, 意味着 不想让 你使用
你 `用 extern 私自使用 ( 虽然可用 static 禁止外部访问, 但 某些系统 可能由于 热补丁方案等需要 而 禁止使用 static )`
-> result:
`破坏 对方想隐藏信息 的 意图 /
引入不恰当的依赖 /
阻碍对方 重构和设计演进`
3) `头文件 太复杂`, 一旦 #include 它, 可能带来
庞大的 `编译开销`
莫名奇妙 的 编译/运行错误
为引入 非自完备 头文件, 还得去找 它所依赖的 其他头文件
理由合理, 解决办法却很粗糙
(2) 1/3 result:
同一函数 2 处声明 => 违背了 DRY 原则
若 对方 func 原型 发生变化
, 而 你的 源代码 无法感知
到: compiler 和 linker 都没发出警告
, 运行时, 按 extern 声明方式 调用
, 却出现异常
1) compiler:
只据 func declaration 编译 => 无法感知
2) linker:
1> C++ linker
+ `func declaration 放 extern "C" 内`
=> symbol table 中 func_name 是 原 func_name 统一加 下划线
=> `linker 无法感知`
2> C++ linker
+ `func declaration 不放 extern "C" 内`
=> C++ compiler 按 func signature “名字粉碎”
symbol table 中 func_name 与 func signature 紧密相关
=> `linker 可 感知 func 原型变化`
然后, `你就 不得不牺牲 本来应该和家人共度的时光,
没日没夜的加班 来解决 这个本来在 编译阶段
就可以 发现和避免的问题`
(3) 1/3 solution:
被依赖 .h
属 自己团队
, 则 重构
, 直到它 简单、正确、自完备
属 `别的团队, 用 / 提改进措施 / 当 bug 提问题单`
2. 自己在 头文件 里 暴露给别人 的 func declaration 要不要用 extern ?
语法上没错, 却完全多余
func declaration 默认 存储类型 为 extern
#ifndef __FOO_H__
#define __FOO_H__
extern int foo(void);
#endif
2.4 extern + 函数定义: extern 语法上没错, 却完全多余
`只 记住标题 含义即可`
extern int foo(void)
{
return 0;
}
summary:
extern 满天飞的时候, 项目往往已处于 糟糕的状态
事实上, 除 `极少数场景外`:
1) 调用 non-inline 汇编 函数`
2) 使用 compiler/linker 生成的 symbol
其他 extern 的使用 都归于 滥用, 至少是无用
几乎没有真正需要 extern 的场景
一些场景下, 它是多余的;
另一些场景下, 用它所带来的 潜在问题,
大大超出 它所带来的 即时便利
实践中, 关掉这扇“后门”, 克服掉 extern 依赖症,
最终会让你的团队受益匪浅
3 充分利用 static 来改善设计
1. static 和 extern 3 种角度 对比:
1) 语法角度
都属 `存储类别 限定符`
2) 语义角度
指定 `完全互斥` 的约束
3) 工程价值 上
`天壤之别`
2. 解耦
(1) 方法
`信息隐藏: 隐藏 client 不需要 care 的信息,
仅让 client 依赖他应该依赖的 东西`
(2) how?
static: `强行隐藏` 模块间和模块内 `不需要扩散的信息`
static 是 C 语言 提供的 `唯一` 用来 `达到这一目标` 的机制
3.1 static + global variable
延长生存期 + 限制作用域
1. 设计良好 的 system, 应该是 利用一系列抽象, 由 behavior 组合起来 的产物
不加约束的使用 global variable `严重违背这一原则`;
但 `基于性能, 存储, 及实现的 简便性` 等 方面的考虑,
global variable 有时确实是较好的选择
如何设计 good system?
(1) static + global variable
, 使 可见范围 仅限
于 定义它的 编译单元内
从语言机制上 杜绝了
other 编译单元 中 用 extern 引入 此变量的可能
别人 破坏封装 的可能
(2) 通过 `模块划分 和 代码组织` 可以很容易
将 全系统 可见的 global variable
转化为
仅在 模块/编译单元内 可见
的 “global variable” -> 对 该 variable 的 所有操作
都 高内聚
到 一个 模块内
//1. Framework.c
//(1) static 杜绝了 other 编译单元(如 App.c) 用 extern 引入 此变量的可能
static int gudVar;
void setValue(int var) { gudVar = var;}
void modifyValue(int var){ gudVar = 3 * var; }
//(2) 2 interface func:
void Framework(int var, int operateFlag)
{
if(operateFlag == 0)
setValue(var);
else
modifyValue(var);
}
int getValue() { return gudVar;}
//2. Framework.h
#ifndef _FRAMEWORK_H_
#define _FRAMEWORK_H_
void Framework(int var, int operateFlag);
int getValue();
#endif
//3. App.c
#include "Framework.h"
int App(int var1, int var2)
{
Framework(3, 0);
printf("\d\n", getValue() );
Framework(3, 1);
printf("\d\n", getValue() );
}
3.2 static + local variable
延长生存期
`1. global / local variable + static:`
限制 变量 `可见范围` 为 `当前编译单元 / 函数内部`
`区别:` 仅 `访问域 不同`
其他则相同,
都在 `数据段`
`都 用来 永久的保存状态`
`2. 重构 思路:`
需 永久保存状态 的 variable 只被一个 function 操作
, 则 把它 从 global scope 转移到 该 function 内部
=> 既可 使其 使用范围一目了然, 又可 防止 被误用
3. local variable non-static 时,
每次函数 被调用时 / 调用结束后,
都 重新从栈中分配 / 将空间归还给栈空间
3.3 static + function
1. 与 static + global variable 目的/结果 相同
2. 模块内 static implement func / non-static interface func
不可/可 被 其他模块 直接调用
`模块: 功能高度内聚的 软件功能模块`
//1. framework.c
// static/non-static func, 允许/不允许 client 直接调用
static int
implement(int var1, int var2)
{
if( var2 != 0 )
return( var1 / var2 );
return 0;
}
int
interface(int var1, int var2)
{
// do_sth();
return implement(var1, var2);
}
//2. framework.h
#ifndef _FRAMEWORK_H_
#define _FRAMEWORK_H_
int interface(int var1, int var2);
#endif /* _FRAMEWORK_H_ */
//3. client.c
#include "framework.h"
int main()
{
printf("%d\n", interface(10, 2) );
}
3.4 static 具有 潜在的 namespace 作用
static 限制了 variable 和 func 的 可见范围
, 所以 不同模块 的 内部实现 可用 same name
, 而不会造成 链接时 symbol conflict
C语言 没有 explicit namespace 概念,
所有 external symbol 都在一个 namespace 里
3.5 热补丁 + 禁止 static
热补丁: 原有函数 的 重新实现, 放 新 编译单元, 却要引用 原有 variable / function
热补丁 编译单元: 不可见 原编译单元 中 static global variable/func, 若 redefine 同名 variable/func
, compiler 会生成 新对象
-> 系统出错
1. 为提高竞争力, 大部分 7*24 小时的 服务器系统 (比如TMM LTE+) 都具备
`不 重启系统 就能 给系统打补丁 的功能, 以 修复缺陷 `
这种方式被称为 `热补丁 / hot patching`
`热: 运行时 对系统 打补丁`, 要求 方案 不能有副作用:
`打上/卸载 热补丁, 则 热补丁操作生效 /系统还原 到原来工作状态`
2. `static global variable/func`
(1) 对 其他编译单元 不可见
=> `global/external symbol table` 中 `不存在 它们的 name`
=> `热补丁 编译单元 无法 访问它们`
(2) 若 `补丁 编译单元内 redefine 它们`,
则 compiler 会生成 `新对象`, 和 系统中 原有对象不同
=> 系统出错
=> 相关团队 往往规定 `无条件禁止使用 static`
3. 为满足 商业价值, 牺牲一些 编程便利性 应该是值得的
但, 它们真的水火不容吗?
3.6 用 宏
在 Dev/Release 两种 版本下 切换
构建 Dev / Release 版本
时 不设 / 设 宏 __RELEASE_ENV__ = 1
, 从而 将 宏 INTERNAL 替换
为 static / 空
global variable / function 前 使用 宏
// if 条件成立时, 宏 INTERNAL 为空
#if defined(__RELEASE_ENV__) && (__RELEASE_EVN__ != 0) // 发布版本
#define INTERNAL
#else
#define INTERNAL static
#endif
INTERNAL int g_var = 0;
INTERNAL int
foo()
{
return 0;
}
虽然 `Release 版本` 中 `global variable / function 最终都是 non-static`,
但 `开发 版本` 中我们 `通过设计`,
已将 `global variable / function` 的使用 `局限于 当前编译单元内`
它们 `实际上等价于 用 static 修饰`
`本节后续 不用记, 理解即可`
`1. Release/Dev 版本 中 需/不需 热补丁`
`Dev 版本` 中 `是否可 继续用 static`,
让 static 时时约束和规范 我们, 帮助我们进行 更好的设计呢?
答: 是
方法: 用 `宏` 在 `Dev/Release 两种 版本下 切换`
`(1) Dev 版本` 时, INTERNAL `自动替换为 static`
`(2) 构建 发布版本` 时, 只需再加上一条 ` __RELEASE_EVN__ = 1`
compiler 会 `把 INTERNAL 替换为空`, 热补丁 下不含 static
`2. 编译时设置宏`:
//1) 等价于 给代码加第1行 #define TRUE 1
gcc test.c -o test -DTRUE
//2) #define macro string
gcc test.c -o test -Dmacro=string
3.7 special 方案给 special local variable
1. local variable, static
影响 可见范围 / 分配方式 / 状态永久性
不能用 宏 INTERNAL 替换
2. special 方案
(1) 升 local variable 为 global -> OK, 但不完美:
失去了 static local variable 所带来的价值
(2) 把 static local variable 所在函数 降格
为 没有实在逻辑
的函数, 把 真正逻辑
提取到 参数化 新函数
unsigned int
handle(unsigned int id)
{
static IdHandlerMap mapTable[] =
{
{ID_FOO, handle_foo},
{ID_BAR, handle_bar}
};
size_t i = 0;
for(i = 0; i < SIZEOF_ARRAY(mapTable); i++)
{
if(id == mapTable[i].id)
return mapTable[i].handler();
}
return FAILED;
}
重构
// 提取 真正逻辑 到 参数化函数
INTERNAL unsigned int
__handle
( unsigned int id,
IdHandlerMap* mapTable,
size_t sizeOfTable)
{
size_t i = 0;
for(i = 0; i < sizeofTable; i++)
{
if(id == mapTable[i].id)
return mapTable[i].handler();
}
return FAILED;
}
// static local variable 所在函数
unsigned int
handle(unsigned int id)
{
static IdHandlerMap mapTable[] =
{
{ID_FOO, handle_foo},
{ID_BAR, handle_bar}
};
return __handle( id, mapTable
, SIZEOF_ARRAY(mapTable) );
}
3. 好处
(1) 重构 后的 原函数 `没 任何逻辑`,
错误只可能发生于 `data 错误 和 func 原型 错误`,
而 热补丁 不支持 修改 原 data 和 func 原型
=> 以这种方式 `用 static 不会给 热补丁 带来实质性影响`
(2) `原 带逻辑的, 可打补丁的 func` 变成 `没副作用的 func / state free`.
若 `原逻辑 出错`, `打补丁时, 无需 引用系统中 已存在的 global variable`, 补丁更安全
3.8 依然不完美的世界
经过这么多努力, 却仍然没有赢回全部
禁止使用 static, 将失去 static 所带来的 namespace 作用
尽管之前的方案让你 可以
`在 发布环境 和 开发环境 之间 切换`, 但 由于
Release Env 最终没 static
=> func name
要 继续用
团队规定的 前缀规则
note: 本文引用 软件大师 袁英杰 的著作《C In Practice》并
结合一些例子来形成 `知识树`,
禁止转载和用于其他用途
网友评论