美文网首页
C In Practice: extern "C" / exte

C In Practice: extern "C" / exte

作者: my_passion | 来源:发表于2020-10-21 23:57 被阅读0次

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 的 所有操作高内聚一个 模块内

static1.JPG
//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》并
结合一些例子来形成 `知识树`, 
禁止转载和用于其他用途

相关文章

网友评论

      本文标题:C In Practice: extern "C" / exte

      本文链接:https://www.haomeiwen.com/subject/dntymktx.html