美文网首页
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