首页 文章

外部“C”的含义

提问于
浏览
-4

有人可以帮我理解以下陈述吗?为什么在#endif是“{”而不是“#ifdef”之前,这似乎不合逻辑,

  • 如果你有一个用C实现的函数,并想从C调用它 .

1.1) . 如果你可以修改C头文件通常C头文件中的声明被包围

#ifdef __cplusplus
  extern "C" { 
#endif 

   [... C declarations ...] 

#ifdef __cplusplus 
  } 
#endif

使其可用于C .

2 回答

  • 4

    我仍然不确定哪个部分有问题,所以我会解释两者 .

    extern "C" 告诉编译器函数是C函数 . 区别主要在于两种语言内部命名/识别函数的方式; C在函数名中有参数类型"mangled",它在内部用于查找正确的函数(请记住,重载函数是链接器 - 这可能是C不可知的 - 正常的,不同命名的函数) . 如果编译器是C编译器, #ifdef / #endif 对只是跳过 extern "C" (和结束大括号) . 这是必要的,因为 extern "C" 不是C语言的一部分,有点矛盾,编译器会发出错误 .

    我在this paper找到了g和VC名称的解释(这是8年之久,所以细节可能已经改变了,但一般概念已经很好了) .

    我用cygwin gcc / g进行了快速测试 . 考虑以下文件overloaded-funcs.c:

    int f(float x){}
    
    #ifdef __cplusplus
    /////////////////////////////////////////////////
    // these f overloads are  visible only to C++ compilers.
    // A C compiler would not accept two functions with the same
    // name.
    int f(int x){}
    int f(void){}
    void g(void){}
    /////////////////////////////////////////////////
    #endif
    
    
    
    #ifdef __cplusplus
    /////////////////////////////////////////////////
    // this part is visible only to C++ compilers
    extern "C" int h(float){} 
    /////////////////////////////////////////////////
    #endif
    
    
    
    #ifdef __cplusplus
    /////////////////////////////////////////////////// 
    // This part, including an opening brace,
    // is only visible to C++ compilers
    extern "C"
    {   // everything in this block are treated as 
        // C declarations/definitions.
    /////////////////////////////////////////////////
    #endif
    
    
    
    /////////////////////////////////////////////////
    // this part is visible to both C and C++ compilers
    int i(void){}
    /////////////////////////////////////////////////
    
    
    #ifdef __cplusplus
    /////////////////////////////////////////////////
    // This brace is again only visible to C++ compilers 
    // (which have seen the opening brace above as well).
    // C compilers would be confused by a closing brace out of nowhere 
    // because they did not see the opening brace.
    } // closes the extern "C" block
    /////////////////////////////////////////////////
    #endif
    

    首先,我使用gcc的-E选项查看预处理器输出,以了解实际编译器看到的内容 .

    $ gcc -E overloaded-funcs.c
    # 1 "overloaded-funcs.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "overloaded-funcs.c"
    
    int f(float x){}
    # 40 "overloaded-funcs.c"
    int i(void){}
    

    就语言而言,将忽略以 # 开头的行 . 代表真实"code"的行很好地突出了语法 . 我们可以看到预处理器从输入到实际编译器之间消除了 #ifdef __cplusplus 和相应的 #endif 之间的所有内容 .

    然后我实际编译了源文件并使用gnu程序“nm”(根据其手册页“列出来自目标文件的符号”)检查了生成的目标文件 .

    $ gcc -c -o ccompiled.o overloaded-funcs.c && nm --defined-only ccompiled.o
    0000000000000000 b .bss
    0000000000000000 d .data
    0000000000000000 p .pdata
    0000000000000000 r .rdata$zzz
    0000000000000000 t .text
    0000000000000000 r .xdata
    0000000000000000 T f
    000000000000000b T i
    

    右列列出了此目标文件包含的名称 . 我们对其中定义的函数感兴趣 . 第一列是函数地址(偏移量),第二列中的字母表示找到符号的“段” . 函数定义位于“文本”部分 . 我们清楚地看到函数名称f和i . 这是链接器或加载器可以找到它们的名称 .

    现在我使用C编译器进行预处理 . 编译器定义了保留字 __cplusplus ,这使得Cray编译器的#defined out可见:

    $ g++ -E overloaded-funcs.c
    # 1 "overloaded-funcs.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "overloaded-funcs.c"
    
    int f(float x){}
    
    int f(int x){}
    int f(void){}
    void g(void){}
    # 20 "overloaded-funcs.c"
    extern "C" int h(float){}
    # 30 "overloaded-funcs.c"
    extern "C"
    {
    # 40 "overloaded-funcs.c"
    int i(void){}
    # 50 "overloaded-funcs.c"
    }
    

    相关的代码行再次得到了很好的强调 .

    然后我将其编译为C并检查目标文件中的名称

    $ g++ -c -o cppcompiled.o overloaded-funcs.c && nm --defined-only cppcompiled.o
    0000000000000000 b .bss
    0000000000000000 d .data
    0000000000000000 p .pdata
    0000000000000000 r .rdata$zzz
    0000000000000000 t .text
    0000000000000000 r .xdata
    0000000000000000 T _Z1ff
    000000000000000b T _Z1fi
    0000000000000014 T _Z1fv
    000000000000001a T _Z1gv
    0000000000000020 T h
    000000000000002b T i
    

    函数名称变得复杂得多 - "mangled" . 我们仍然可以在名称中间看到"actual"名称 fg ,前缀为_Z1 . 显然,参数类型只是编码为"actual" name部分后面的void,int和float的单个字母v,i和f .

    请注意,返回值不是生成的函数名称的一部分,这意味着链接器通常不知道它(通常没有除目标文件之外的其他信息) . 这与语言规则一致,即返回值不考虑重载解析(并且不可能有两个同名的函数,它们只在返回值上有所不同) . 就链接器而言, f 的重载版本是完全不相关的函数 .

    我们还可以看到声明为 extern "C" 的函数具有旧的C名称( hi ) . 另一个翻译单元中的C代码可以声明这些函数并使用它们,链接器将找到该符号( ih ),解析依赖关系并将函数的代码添加到可执行文件中 . 但是,这样的C代码不能链接到函数 f ,因为只要链接器可以看到我们的目标文件中不存在这样的函数 .

    很明显,C更安全 . 如果使用错误的参数类型声明C函数,则链接器可以快速地链接到期望完全不同的参数的实现 . 它无法知道函数的期望 . 在C中,链接器根本找不到具有不同参数的实现,因为它们的类型是在损坏的名称中编码的 .

  • 0

    如果已经定义了__cplusplus,那么它就是C代码我们想要

    extern "C" {
    

    并关闭它

    }
    

    在末尾 . 我希望我已正确解码你的消息 .

相关问题