【问题标题】:Is it required to add 'extern C' in source file also?是否还需要在源文件中添加“extern C”?
【发布时间】:2011-01-11 05:07:17
【问题描述】:

我最近发现了一些代码,其中在源文件中添加了 extern "C" 也用于函数。它们也被添加到声明它们的头文件中。

我假设在头文件中添加 'extern "C" 就足够了。

应该在哪里添加外部“C”块?

更新: 假设我正在使用 CPP 编译器编译我的 C 代码,并为头文件中的所有函数添加了 extern "C" 保护(即我的所有函数在头文件中都有它们的原型),但在源文件中我没有添加相同的。这会导致问题吗?

【问题讨论】:

  • 肯定在标题中。我曾经签订过一份合同,他们的编码标准要求在源代码中而不是在标题中包含守卫。神圣的样板,蝙蝠侠!
  • @gbacon:这是 Lakos 关于大规模 C++ 编程的书中的建议。这个想法是包含警卫需要阅读所有标题。当然,现在 gcc 会识别包含守卫,而不是读取整个 .h 文件。
  • 你所说的“C”警卫是什么意思?包括守卫或外部“C”块?
  • 它是外部“C”块。

标签: c++ c


【解决方案1】:

你的意思是

extern "C" { ... }

样式保护,这些声明一些函数是“C”链接,而不是“C++”链接(通常有一堆额外的名称装饰来支持重载函数等内容)。

当然,目的是让 C++ 代码与 C 代码交互,而 C 代码通常位于库中。如果库的标头不是用 C++ 编写的,那么它们将不会包含 C++ 的 extern "C" 保护

使用 C++ 编写的 C 标头将包含类似于以下内容的内容

#ifdef __cplusplus
extern "C" {
#endif

...

#ifdef __cplusplus
}
#endif

以确保 C++ 程序看到正确的链接。然而,并不是所有的库都是用 C++ 编写的,所以有时你必须这样做

extern "C" {
#include "myclibrary.h"
}

获得正确的链接。如果头文件是由其他人提供的,那么更改它不是一个好习惯(因为那样你不能轻易地更新它),所以最好用你自己的保护来包装头文件(可能在你自己的头文件中)。

extern "C" 不是 (AFAIK) ANSI C,因此不能在没有预处理器保护的情况下包含在普通 C 代码中。

响应您的编辑:

如果您使用的是 C++ 编译器,并且在头文件中将函数声明为 extern "C",则无需在实现文件中将该函数声明为 extern "C"。来自 C++ 标准的第 7.5 节(重点是我的):

如果两个声明相同 函数或对象指定不同 链接规范(即 这些的链接规范 声明指定不同的 字符串文字),程序是 如果声明出现,则格式错误 在同一个翻译单元中,并且 一定义规则 如果声明出现在 不同的翻译单位。除了 对于具有 C++ 链接的函数,a 没有链接的函数声明 规范不得先于 第一个链接规范 功能。 可以声明一个函数 之后没有链接规范 显式链接规范具有 被看见;明确的联系 在较早的声明中指定 不受此类功能的影响 声明。

但我不认为这是一个好的做法,因为链接规范可能会意外发生分歧(例如,如果包含链接规范的头文件未包含在实现文件中)。我认为最好在实现文件中明确。

【讨论】:

  • 静态函数呢?头文件中不会有声明,所以我们应该在源文件中为其添加 extern "C" 吗?我说的是使用 CPP 编译器编译的 C 代码。
  • 任何需要链接到当前编译单元之外的函数都必须声明正确的链接。静态函数(只能在当前 c 文件中看到的 IE 函数)无关紧要,因为该函数不会导出,因此永远不需要链接。
  • 我得到了你指出的问题,因为“包含链接规范的头文件不包含在实现文件中”,所以我完全同意你,最好在实施文件,顺便说一句,你为我节省了很多时间,谢谢 :)
【解决方案2】:

他们只需要进入其他源文件包含的任何内容。

使用some idioms,您可以找到包括源文件在内的人员。

【讨论】:

    【解决方案3】:

    应该将它们添加到所有文件中,这些文件包含在其他文件中。

    通常不包含源文件。

    【讨论】:

      【解决方案4】:

      道歉

      问题已经变得更加清晰,它所问的是什么。这个答案解决了最初的问题,当时至少有争议的是它是否正在讨论防止头文件中的多重包含 - 这就是我的答案所针对的。显然,如果问题像现在这样清楚,我就不会提交这个答案。


      原答案

      不,没有必要在 C 代码中也包含守卫。

      如果头文件'header.h'说:

      #ifndef HEADER_H_INCLUDED
      #define HEADER_H_INCLUDED
      ...
      #endif
      

      那么源文件'source.c'说是完全安全的:

      #include "header.h"
      

      其他标头包含“header.h”也是安全的。

      但是,人们注意到打开头文件并读取它需要时间,这会减慢编译速度,因此有时人们会这样做:

      #ifndef HEADER_H_INCLUDED
      #include "header.h"
      #endif
      

      这意味着如果“source.c”中包含的其他一些头文件已经包含“header.h”,则不会重新处理“#include”。 (或者,如果 'header.h' 已经直接包含在 'source.c' 中,尽管这是一个愚蠢的 buglet。)

      所以,遇到的时候,很可能是为了优化编译性能。远不清楚它能给你带来多少。现代 C 预处理器在这个问题上相当聪明,并且会尽可能避免重新包含文件。并且总是存在'source.c'中的测试有错字(#ifndef HEARER_H_INCLUDED,也许是)的风险,在这种情况下,测试会减慢编译速度,因为预处理器会测试不相关的条件,然后在之后继续包含'header.h'全部。它是“安全的”;标头本身是受保护的——或者应该是。

      如果您看到“source.c”中的代码也在执行“#define HEADER_H_INCLUDED”,那么就有问题了。 #define 必须在#include 之前或之后,两者都不是通用技术。

      • 如果 'source.c' 在包含 'header.h' 之前执行了 '#define HEADER_H_INCLUDED',那么如果防护出现在 'header.h' 中,则不会包含头的内容。如果守卫没有出现在“header.h”中,那么一切正常。
      • 如果 'source.c' 在包含 'header.h' 后执行了 '#define HEADER_H_INCLUDED',那么如果守卫出现在 'header.h' 中,我们会得到 HEADER_H_INCLUDED 的良性重新定义。如果 'header.h' 不包含保护,但包含一个包含 'header.h' 的文件,那么您毕竟不会受到多重包含的保护。

      请注意,标题正文出现在“#define HEADER_H_INCLUDED”之后。如果嵌套包含 include 'header.h',这又是一种保护。

      【讨论】:

      • 他在问extern "C",不包括警卫。
      • @jalf:现在的问题是关于 'extern "C"' 守卫;昨天我回答时,它显然没有这样做。
      • 我知道。我刚刚发布了评论以供您参考,因此您有机会编辑您的答案以回答 OP 实际要问的问题。 :)
      • @jalf:好的 - 感谢您的提醒。我可能会推迟到有关“外部“C””的其他答案,而不是添加我自己的答案。我可能会留下标记的答案。
      • @Jonathan,我很抱歉没有把问题说清楚。
      【解决方案5】:

      您的意思是“extern c”预处理器?它们必须在函数定义上,并且会影响函数调用在编译后的二进制文件中的存储方式。仅当您将编译后的 c++ 与编译为 C 的 c 链接在一起时才真正需要它(而不是 .cpp 文件中的 c)。

      【讨论】:

        【解决方案6】:

        “C”守卫有两个目的:

        1. 编译代码后,函数将以允许非 C++ 编译器/链接器使用它们的方式导出(无 C++ 名称修改等)
        2. 当 C++ 编译器使用您的头文件时,它将知道它应该以 C 方式绑定符号,从而确保生成的程序将成功链接。它们对非 C++ 编译器没有意义,但由于符号是在 (1) 中以 C 样式生成的,因此这是预期的效果。

        由于您在实现文件中也包含带有“C”保护的标头,因此编译器可以使用有关如何在编译时创建符号的信息,并且编译器将创建符号的方式可以由非 C++ 编译器使用。因此,只要头文件也包含在实现文件中,您只需在头文件中指定 extern "C"

        【讨论】:

        • 这是否意味着对于没有在头文件中声明的静态函数,我们需要在源文件中添加 extern "C" 保护?
        • 文件静态函数将(也不能)从外部模块调用,无需将它们标记为 C 链接。文件静态函数调用在编译时解析。
        • 但是,假设我尝试使用 C++ 编译器编译我的“C”代码。还能用吗?
        • 是的,C++(大部分)是 C 的超集。
        【解决方案7】:

        如果在头文件中使用 extern 并且该文件包含在其余源文件中,则不需要在源文件中使用 extern。

        据我所记得的标准,所有函数声明默认都被认为是“extern”,所以没有必要明确指定。这不会使这个关键字无用,因为它也可以与变量一起使用(在这种情况下 - 它是解决链接问题的唯一解决方案)。但是有了这些功能 - 是的,它是可选的。

        更详细一点的答案是,它允许您使用在另一个源代码文件中编译的变量,但不为变量保留内存。因此,要使用 extern,您必须有一个源代码文件或一个库单元,其中包含顶层变量(而不是函数内)的内存空间。现在,您可以通过在其他源代码文件中定义同名的 extern 变量来引用该变量。

        一般来说,应该避免使用外部定义。它们很容易导致难以管理的代码和难以定位的错误。当然,也有其他解决方案不切实际的例子,但它们很少见。例如,stdin 和 stdout 是映射到 stdin.h 中 FILE* 类型的外部数组变量的宏;此数组的内存空间位于标准 C 库单元中。

        【讨论】:

        • 这不是问题要问的。问题是关于使用 {extern "C"} 而不是使用 {extern}
        【解决方案8】:

        我们一直只在头文件定义中添加 extern "C",但这允许 C++ 代码通过重载实现具有不同签名的函数而不会出错,但它不会破坏符号定义,因此在链接时它使用这个不匹配函数。 如果标头和定义都具有 extern "C",则签名不匹配会在 Visual Studio 2017 和 g++ 5.4 中生成错误。

        以下代码在 Visual Studio 2017 和 g++ 5.4 上编译时没有错误

        extern "C" {
            int test(float a, int b);
        }
        int test(int b)
        {
            return b;
        }
        

        在这种情况下,gcc 似乎会破坏符号名称,但 Visual Studio 2017 不会。

        但是在定义中包含 extern "C" 会在编译时捕获不匹配。

        extern "C" {
            int test(float a, int b);
        }
        extern "C" int test(int b)
        {
            return b;
        }
        

        在 g++ 上给出以下错误:

         g++ -c -o test.o test.cpp
         test.cpp: In function ‘int test(int)’:
         test.cpp:4:26: error: conflicting declaration of C function ‘int test(int)’
         extern "C" int test(int b)
                              ^
         test.cpp:2:9: note: previous declaration ‘int test(float, int)’
         int test(float a, int b);
        

        或用 cl

        cl /c test.cpp
        Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27042 for x86
        Copyright (C) Microsoft Corporation.  All rights reserved.
        
        test.cpp
        test.cpp(4): error C2733: 'test': second C linkage of overloaded function not allowed
        test.cpp(2): note: see declaration of 'test'
        

        【讨论】:

          猜你喜欢
          • 2019-12-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-10-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多