5

I'm writing a C library, which may potentially be useful to people writing C++. It has a header which looks like this:

#ifndef FOO_H_
#define FOO_H_

#include <bar.h>

#include <stdarg.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

void foo_func();

#ifdef __cplusplus
}
#endif
#endif 

and I was wondering - should I move the extern "C" bit before including the header include directives? Especially seeing how, in practice, some of those headers might themselves have an extern "C"?

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 3
    The standard library headers already have that inside them where necessary. Worry about it for your own declarations. – user207421 Oct 20 '21 at 11:02
  • @user207421: I expanded the question scope slightly, so that an answer might address both standard-library and other headers. Also, what guarantees the standard library headers will have extern C where necessary? Is that a requirement in the language standard? – einpoklum Oct 20 '21 at 11:09
  • 1
    Some headers are explicitly written under the assumption that the outermost "scope" has C++ language linkage. They may use `__cplusplus` to remove template declarations, and if you wrap them up like you wish to, your header will be fundamentally broken. So as mentioned already, make *your* declarations correct, and let other headers do their thing unimpeded. Even standard library headers may break (since an implementer may reason its going to be shared anyway, and then do some expert friendly things inside). – StoryTeller - Unslander Monica Oct 20 '21 at 11:09

3 Answers3

2

NO, in general you shouldn't move it to include the headers.

extern "C" is used to indicate that the functions is using the C calling convention. The declaration has no effect on variables and #defines, so there is no need to include these. If an #include is inside an extern "C" block, this effectively modifies the function declarations inside that header file!

Background: Without the extern "C" declaration, when compiling using a C compiler, a function is assumed to follow the C convention, and when compiling using a C++ compiler, the C++ convention is assumed. In case the same header file is used for C and C++ code, you would get a linker error, since the compiled function has different names in C and C++.

Although it's possible to put all your code between the #ifdef blocks, I personally don't like it, because it's really only intended for the function prototypes, and I frequently see people copy-pasting this at places where it shouldn't be. The cleanest way is to keep it where it's supposed to be, which is around the function prototypes in a C/C++ header file.

So, to answer your question "should I move the extern "C" bit before including the header include directives?", my answer is: No, you shouldn't.

But is it possible? Yes, and in many cases this won't break anything. Sometimes it is even necessary, if the function prototypes in an external header file are incorrect (e.g. when they are C functions and you want to call them from C++) and you cannot change that library.

However, there are also cases where doing so breaks the build. Here's a simple example that fails to compile if you wrap the include using extern "C":

foo.h:

#pragma once

// UNCOMMENTING THIS BREAKS THE BUILD!
//#ifdef __cplusplus
//extern "C" {
//#endif

#include "bar.h"

bar_status_t foo(void);

//#ifdef __cplusplus
//}
//#endif

foo.c:

#include <stdio.h>
#include "foo.h"
#include "bar.h"

bar_status_t foo(void)
{
    printf("In foo. Calling bar wrapper.\n");
    return bar_wrapper();
}

bar.h:

#pragma once

typedef enum {
    BAR_OK,
    BAR_GENERIC_ERROR,
    BAR_OUT_OF_BEAR,
    // ...
} bar_status_t;

extern "C" bar_status_t bar_wrapper(void);
bar_status_t bar(void);

bar.cpp:

#include <iostream>
#include "bar.h"

extern "C" bar_status_t bar_wrapper(void)
{
    std::cout << "In C/C++ wrapper." << std::endl;
    return bar();
}

bar_status_t bar(void)
{
    std::cout << "In bar. One bear please." << std::endl;
    return BAR_OK;
}

main.cpp:

#include <stdio.h>
#include <stdlib.h>
#include "foo.h"
#include "bar.h"

int main(void)
{
    bar_status_t status1 = foo();
    bar_status_t status2 = bar();
    return (status1 != BAR_OK) || ((status2 != BAR_OK));    
}

When uncommenting the blocks in a.h, I get the following error:

main2.cpp:(.text+0x18): undefined reference to `bar'
collect2.exe: error: ld returned 1 exit status
Makefile:7: recipe for target 'app2' failed

Without, it builds fine. A C main calling only C functions from foo and bar will build fine either way, since it's unaffected by the #ifdef __cplusplus blocks.

wovano
  • 4,543
  • 5
  • 22
  • 49
  • "Only if you're including an external library that was written in C and does not contain the mentioned C++ directives" <- Well, if I'm including an external library, obviously it's written in C (otherwise I wouldn't include). And now, if it does contain extern C - then nesting it doesn't hurt; and if it doesn't contain extern C - then covering the inclusion with extern C should help. I therefore don't understand your argument. – einpoklum Oct 20 '21 at 11:31
  • @einpoklum "_Well, if I'm including an external library, obviously it's written in C (otherwise I wouldn't include)_" Why is that? You could also include a C++ library. I don't know if you're currently programming in C or C++ since the question is tagged with both, but it doesn't really matter, the `extern "C"` is only needed when mixing both languages. – wovano Oct 20 '21 at 11:42
  • @wovano: *Why is that?* Because `foo` is written in C. Its source only includes other C language headers. But I'll clarify this in the question text. – einpoklum Oct 20 '21 at 11:45
  • @einpoklum, if `foo` is written in C, then your current header is indeed how I would write it :-) – wovano Oct 20 '21 at 11:50
0

Surprisingly yes. After reading the standard now even I would write

#ifndef FOO_H_
#define FOO_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <bar.h>

#include <stdarg.h>
#include <stddef.h>


void foo_func();

#ifdef __cplusplus
}
#endif
#endif 

For 2 reasonss:

1 Having nested extern "C" are no problem, so your bar.h include would be fine. Like this it looks clearer and more important

2 To be really portable your surprisingly have to wrap an extern "C" around your C headers for C++ users, if they don't want to.

Because I just looked thru the C++ Standard and 16.5.2.3 Linkage [using.linkage] states

Whether a name from the C standard library declared with external linkage has extern "C" or extern "C++" linkage is implementation-defined. It is recommended that an implementation use extern "C++" linkage for this purpose.1

To be safe than sorry, you indeed should wrap an extern "C" around those includes but, you should not have to, since this implies to the headers in D.9 C headers [depr.c.headers] like <stdlib.h> which you are using.

Superlokkus
  • 4,731
  • 1
  • 25
  • 57
  • Well, yes, but I don't understand the but. Naturally, in C++, you're encouraged to use `` rather than ``. But this fact does not qualify the "yes" AFAICT. – einpoklum Oct 20 '21 at 11:47
  • Its a disclaimer since your question was not clear about whenever you wanted to write a mix of C and C++ for you library and be useable by C and C++ users. One could implement a library in C++ and call/use it from C code/compilers, implement in C and use from C++ and any permutation and even mix in the implementation. Your question was not clear on that. – Superlokkus Oct 20 '21 at 11:50
  • 1
    I think that "implementation" refers to the compiler and corresponding libraries & headers. It is not referring to you and me (assuming you are not writing compilers ;p). I think this means that a C++ compiler is allowed to implement the standard C library in C++, with C++ linkage, but of course the corresponding headers should correspond to the code. So it won't have any effect on applications using these libraries (and headers). – wovano Oct 20 '21 at 12:09
  • @wovano If tend to my interpretation because I saw some "this is how the C headers and C std library in C++ differs from acutal C" including "we stuff that into that namespace or with std" but never "C++ implementations shall enclose the C library headers in the well known courtesy extern wrappers" in the C++ standard. But you might find it :-) – Superlokkus Oct 20 '21 at 12:24
  • 2
    ***Surprisingly yes.** ...* IMO that would be one badly-broken implementation, though. I might want to not put the standard C headers inside the C++ "guards" just so it WOULD blow up and I'd be able to say, "We don't support your implementation." – Andrew Henle Oct 20 '21 at 12:24
  • 1
    Ah yes Stackoverflow where opinions are more valuable than evidence or conclusion from sources/references/documentation/standards. – Superlokkus Oct 22 '21 at 06:12
  • 1
    I was surprised about that as well. I gave an answer in which I prove that always wrapping headers with `extern "C"` ***can*** break a build. The other answer provided a link to a reliable source in which the questions were answered clearly. Yet an answer that mentions "it looks more important" as a reason gets more upvotes... – wovano Oct 22 '21 at 07:27
  • 1
    @wovano Hey your answer has more up votes than mine (at least in sum) :-) . The thing is sometimes even good FAQs from a reliable resource is not aware of some standard suprises, especially for seldom relevant/used things. But I admit the reliable FAQ indicates that my interpretation is wrong. However some here just claim stuff without looking into the Standard or Implementation docs/offical disucssions themselfs. No quarrel with you :-) – Superlokkus Oct 22 '21 at 08:11
0

This is covered in the C++ FAQ.

First, How to include a standard C header file in C++ code? Nothing special is needed, as standard C header files work seamlessly with C++. So you don't need to wrap stdarg.h and stddef.h in extern "C".

Then, for non-standard C headers, there are two possibilities: either you can’t change the header, or you can change the header.

When you can't change the C header, wrap the #include in extern "C".

// This is C++ code
extern "C" {
  // Get declaration for f(int i, char c, float x)
  #include "my-C-code.h"
}
int main()
{
  f(7, 'x', 3.14);   // Note: nothing unusual in the call
  // ...
}

When you can change the header, edit it to conditionally include extern "C" in the header itself:

#ifdef __cplusplus
extern "C" {
#endif

. . .

#ifdef __cplusplus
}
#endif

In your case it comes down to whether you have control over the contents of bar.h. If it's your header, then you should modify it to include extern "C" and not wrap the #include itself.

Headers should work the same way regardless of how/when/where they are included. Wrapping an #include in extern "C", #pragma pack, special #defines, etc, should be reserved for last resort workarounds, as this may interfere with how the header behaves in different scenarios, reducing the system's maintainability in the long run.

As StoryTeller said in the comments:

Some headers are explicitly written under the assumption that the outermost "scope" has C++ language linkage. They may use __cplusplus to remove template declarations, and if you wrap them up like you wish to, your header will be fundamentally broken. So as mentioned already, make your declarations correct, and let other headers do their thing unimpeded. Even standard library headers may break (since an implementer may reason its going to be shared anyway, and then do some expert friendly things inside).

Note that standard C headers may be implemented using C linkage, but may also be implemented using C++ linkage. In which case wrapping them in extern "C" might result in link errors.

rustyx
  • 80,671
  • 25
  • 200
  • 267
  • That FAQ is directed at the library _user_, who's writing C++; I'm the library _author_, writing C. – einpoklum Oct 20 '21 at 12:34
  • I would say §16.5.2.3 Linkage [using.linkage] from the C++ standard trumps a FAQ, even on the isocpp website. – Superlokkus Oct 20 '21 at 12:36
  • @einpoklum, the FAQ is also directed at library writers. Your question is basically answered in this question: [How can I modify my own C header files so it’s easier to #include them in C++ code?](https://isocpp.org/wiki/faq/mixing-c-and-cpp#include-c-hdrs-personal) (And the question above it describes what you could do if a work-around if the to-bo-included header was not written this way.) – wovano Oct 20 '21 at 13:15
  • @Superlokkus [\[using.linkage\]/2](https://timsong-cpp.github.io/cppwp/n4659/using.linkage#2) says "*Whether a name from the C standard library declared with external linkage has extern "C" or extern "C++" linkage is implementation-defined.*", which means we can't know what linkage the standard library uses and should thus refrain from wrapping it in any `extern`-anything. There is no discrepancy between what the standard says and the FAQ. Just don't do it. – rustyx Oct 20 '21 at 13:56