最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - anonymous namespace and extern "C" linkage question - Stack Overflow

programmeradmin1浏览0评论

My use-case is as follows for a project:

  • A C header file with a few extern functions declared
  • A C++ source file with those functions defined
  • Compile the C++ source into a shared library
  • In a C source file use the functions declared in C header by linking against the .so

I am observing something strange while doing do. Things start to work with unnamed namepsace.

Here's my sample file:

c_sample.h:

#include "stddef.h"

extern void hello(void);
extern void bye(void);

cpp_sample:

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

extern "C" {
    void hello(void) { std::cout << "HI" << std::endl; }
    void bye(void) { std::cout << "BYE" << std::endl; }
}

On trying to build a share lib, I see the error which is expected since c_sample.h is included outside of the extern "C" block.

g++ cpp_sample -shared -o libcppsample.so     
cpp_sample:5:7: error: declaration of 'hello' has a different language linkage
    5 |         void hello() { std::cout << "HI" << std::endl;}
      |              ^
./c_sample.h:3:13: note: previous declaration is here
    3 | extern void hello();
      |             ^
cpp_sample:6:7: error: declaration of 'bye' has a different language linkage
    6 |         void bye() { std::cout << "BYE" << std::endl;}
      |              ^
./c_sample.h:4:13: note: previous declaration is here
    4 | extern void bye();
      |             ^
2 errors generated.

However, magic happens the moment I wrap this up in a un-named namespace

cpp_sample:

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

extern "C" {
namespace {
    void hello(void) { std::cout << "HI" << std::endl; }
    void bye(void) { std::cout << "BYE" << std::endl; }
}
}

This compiled. And when I tried using it from another C source file, it even works

#include "stdio.h"
#include "c_sample.h"

int main() {
    hello();
}
$ gcc another.c -L/tmp -lcppsample -o another
$ ./another
HI

How does this work just with wrapping it inside a namespace? How is it able to link the declared functions with its definitions?

My use-case is as follows for a project:

  • A C header file with a few extern functions declared
  • A C++ source file with those functions defined
  • Compile the C++ source into a shared library
  • In a C source file use the functions declared in C header by linking against the .so

I am observing something strange while doing do. Things start to work with unnamed namepsace.

Here's my sample file:

c_sample.h:

#include "stddef.h"

extern void hello(void);
extern void bye(void);

cpp_sample:

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

extern "C" {
    void hello(void) { std::cout << "HI" << std::endl; }
    void bye(void) { std::cout << "BYE" << std::endl; }
}

On trying to build a share lib, I see the error which is expected since c_sample.h is included outside of the extern "C" block.

g++ cpp_sample -shared -o libcppsample.so     
cpp_sample:5:7: error: declaration of 'hello' has a different language linkage
    5 |         void hello() { std::cout << "HI" << std::endl;}
      |              ^
./c_sample.h:3:13: note: previous declaration is here
    3 | extern void hello();
      |             ^
cpp_sample:6:7: error: declaration of 'bye' has a different language linkage
    6 |         void bye() { std::cout << "BYE" << std::endl;}
      |              ^
./c_sample.h:4:13: note: previous declaration is here
    4 | extern void bye();
      |             ^
2 errors generated.

However, magic happens the moment I wrap this up in a un-named namespace

cpp_sample:

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

extern "C" {
namespace {
    void hello(void) { std::cout << "HI" << std::endl; }
    void bye(void) { std::cout << "BYE" << std::endl; }
}
}

This compiled. And when I tried using it from another C source file, it even works

#include "stdio.h"
#include "c_sample.h"

int main() {
    hello();
}
$ gcc another.c -L/tmp -lcppsample -o another
$ ./another
HI

How does this work just with wrapping it inside a namespace? How is it able to link the declared functions with its definitions?

Share Improve this question edited Mar 30 at 9:34 Mateusz Grzejek 12.2k3 gold badges34 silver badges51 bronze badges asked Mar 29 at 23:39 nptnpt 331 silver badge5 bronze badges 7
  • 1 I suspect you're running into an implementation-specific behaviour, which happens to give a combined effect that you consider "works". Rather than relying on that (which may well break with other compilers or target platforms) do the more conventional approach - either (1) #include c_sample.h" within an extern "C" block (downside is that EVERY c++ source file that includes it must do the same) or (2) modify the header itself so, when compiled as C++, it adds extern "C" decorations. – Peter Commented Mar 30 at 0:27
  • Can't replicate - I am getting undefined reference to `hello()' in the second option. godbolt./z/zf6ahajqq – 3CxEZiVlQ Commented Mar 30 at 0:33
  • As far as the C++ compiler is concerned, those extern declarations in the header declare different functions than the ones defined in the unnamed namespace. The header doesn't do anything useful, you might as well drop the #include directive. – Igor Tandetnik Commented Mar 30 at 0:35
  • @3CxEZiVlQ The trick is that the caller is a C file, not a C++ file. Name mangling just happens to line up. – Igor Tandetnik Commented Mar 30 at 0:54
  • I think it's well-defined. On the C++ side, it's allowed to define functions with a C linkage in a namespace. The intention is that the C compiler sees a function with the given name, ignoring the namespace. ODR requires that no two C-linkage functions have the same name, even if declared in different namespaces. – Igor Tandetnik Commented Mar 30 at 0:56
 |  Show 2 more comments

2 Answers 2

Reset to default 1

How does this work just with wrapping it inside a namespace? How is it able to link the declared functions with its definitions?

Here is a trivially different reproduction of your failing compilation case:

$ tail -n +1 c_sample.h bad 
==> c_sample.h <==
#ifndef C_SAMPLE_H
#define C_SAMPLE_H

extern void hello(void);
extern void bye(void);

#endif

==> bad <==
#include <iostream>
#include "c_sample.h"

extern "C" {
    void hello(void) { std::cout << "HI" << std::endl; }
    void bye(void) { std::cout << "BYE" << std::endl; }
}

I've merely removed #include "stddef.h" from c_sample.h, since it contributes nothing, and the compilation failure is what you expect and understand:

$ g++ --version | head -n1
g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0

$ g++ -std=c++11 -c bad -Wall -Wextra -pedantic
bad:5:10: error: conflicting declaration of ‘void hello()’ with ‘C’ linkage
    5 |     void hello(void) { std::cout << "HI" << std::endl; }
      |          ^~~~~
In file included from bad:2:
c_sample.h:4:13: note: previous declaration with ‘C++’ linkage
    4 | extern void hello(void);
      |             ^~~~~
rbad:6:10: error: conflicting declaration of ‘void bye()’ with ‘C’ linkage
    6 |     void bye(void) { std::cout << "BYE" << std::endl; }
      |          ^~~
c_sample.h:5:13: note: previous declaration with ‘C++’ linkage
    5 | extern void bye(void);
      |             ^~~

Now here is a C++ source slightly different from your successfully compiled cpp_sample version #2.

$ cat good
#include <iostream>
#include "c_sample.h"

extern "C" {
namespace {
    void hello(void) { std::cout << "HI" << std::endl; }

}
namespace ns {
    void bye(void) { std::cout << "BYE" << std::endl; }
}
}

The difference here is that I've defined bye in a different namespace (ns), from the namespace ( <anonymous>) that contains the definition of hello. That's just to flush out any difference it makes. But it doesn't make any:

$ g++ -std=c++11 -c good -Wall -Wextra -pedantic; echo Done
Done

It compiles clean. The symbol table of the output object file is:

$ readelf -sW good.o

Symbol table '.symtab' contains 15 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS good
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 .text
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 .rodata
     4: 0000000000000007     1 OBJECT  LOCAL  DEFAULT    5 _ZNSt8__detail30__integer_to_chars_is_unsignedIjEE
     5: 0000000000000008     1 OBJECT  LOCAL  DEFAULT    5 _ZNSt8__detail30__integer_to_chars_is_unsignedImEE
     6: 0000000000000009     1 OBJECT  LOCAL  DEFAULT    5 _ZNSt8__detail30__integer_to_chars_is_unsignedIyEE
     7: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _ZSt21ios_base_library_initv
     8: 0000000000000000    54 FUNC    GLOBAL DEFAULT    1 hello
     9: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _ZSt4cout
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _ZNSolsEPFRSoS_E
    14: 0000000000000036    54 FUNC    GLOBAL DEFAULT    1 bye
    

where we can see that the source function names void <anonymous>::hello(void) and void ns::bye(void), have been translated to defined global linker symbols hello and bye without C++ name-mangling, just as required by the extern "C" scope in which they are declared and defined: no namespaces and no calling signatures are encoded in the linker symbols. They're just plain old C symbols, unlike say:

$ c++filt _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)

And so:

$ cat main.c
#include "c_sample.h"

int main() {
    hello();
    bye();
    return 0;
}

$ gcc main.c good.o -lstdc++
$ ./a.out
HI
BYE

is fine.

extern "C" doesn't change the compiler's parsing of C++ grammar. By enclosing the function definitions of hello and bye in a namespace or different namespaces the identifiers are distinguished from the declarations included from c_sample.h, as they always would be. But extern "C" suppresses the C++ name-mangling of the symbols that are emitted in the object code for the namespace-enclosed identifiers in favour of C symbolisation. Then the namespaces are lost, and the calling signatures. All that matters to the linker is that it can find definitions of the referenced symbols in the object files and libraries that are input, hello, bye, _ZSt4cout etc.

We can emphasise that distinction with:

$ cat bad_1
#include <iostream>
#include "c_sample.h"

extern "C" {
namespace {
    void hello(void) { std::cout << "HI" << std::endl; }

}
namespace ns {
    void bye(void) { std::cout << "BYE" << std::endl; }
}
}

namespace {
    void hello(void) { std::cout << "HI" << std::endl; }

}
namespace ns {
    void bye(void) { std::cout << "BYE" << std::endl; }
}

where void <anonymous>::hello(void) and void ns::bye(void) are each defined twice, once within the scope of extern "C" and once not.

$ g++ -std=c++11 -c bad_1
bad_1:15:10: error: redefinition of ‘void {anonymous}::hello()’
   15 |     void hello(void) { std::cout << "HI" << std::endl; }
      |          ^~~~~
bad_1:6:10: note: ‘void {anonymous}::hello()’ previously defined here
    6 |     void hello(void) { std::cout << "HI" << std::endl; }
      |          ^~~~~
bad_1:19:10: error: redefinition of ‘void ns::bye()’
   19 |     void bye(void) { std::cout << "BYE" << std::endl; }
      |          ^~~
bad_1:10:10: note: ‘void ns::bye()’ previously defined here
   10 |     void bye(void) { std::cout << "BYE" << std::endl; }
      |          ^~~
      

The extern "C" scope makes no difference to the C++ parsing: the multiple definitions are illegal C++. We don't even get as far as emitting any symbols, C++-mangled or not. But if they were not illegal and we did get that far, the symbols emitted from default C++ linkage scope would be:

FUNC    LOCAL  DEFAULT    1 _ZN12_GLOBAL__N_15helloEv
FUNC    GLOBAL DEFAULT    1 _ZN2ns3byeEv

and the ones emitted from the extern "C" scope would be as before:

FUNC    GLOBAL DEFAULT    1 hello
FUNC    GLOBAL DEFAULT    1 bye

with no multiple definitions at linktime.

You can see that good is equivalent to:

$ cat good_1
#include <iostream>

extern void hello(void);
extern void bye(void);

extern "C" {
namespace {
    void hello(void) { std::cout << "HI" << std::endl; }

}
namespace ns {
    void bye(void) { std::cout << "BYE" << std::endl; }
}
}

where it's obvious that the declarations:

extern void hello(void);
extern void bye(void);

with C++ linkage by default are simply unrelated to the namespace-enclosed definitions and are redundant. Thus with another C++ source file:

$ cat hellobye 
#include "c_sample.h"

void hello(void) { std::cout << "Hello" << std::endl; }

void bye(void) { std::cout << "Goodbye" << std::endl; }

we can compile and link another program:

$ g++ -std=c++11 -c main.c
$ g++ -std=c++11 -c hellobye  
$ g++ main.o good.o hellobye.o  
$ ./a.out
Hello
Goodbye

in which they're not redundant. Here, compiling with g++, main.c is compiled as C++ source (it saves me copying the same file with extension) and the declarations from c_sample.h have C++ linkage by default: i.e. they'll output C++-mangled symbols. So we see that:

$ readelf -sW main.o

Symbol table '.symtab' contains 6 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 .text
     3: 0000000000000000    25 FUNC    GLOBAL DEFAULT    1 main
     4: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z5hellov
     5: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z3byev
     

main.o makes external references to the C++-mangled symbols _Z5hellov and _Z3byev, which are demangled:

$ readelf -sCW main.o | egrep \(hello\|bye\) 
     4: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND hello()
     5: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND bye()
     

and are defined in:

$ readelf -sW hellobye.o | egrep \(hello\|bye\) 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hellobye
     8: 0000000000000000    54 FUNC    GLOBAL DEFAULT    1 _Z5hellov
    14: 0000000000000036    54 FUNC    GLOBAL DEFAULT    1 _Z3byev
    

The symbol table of this program:

 readelf -sW a.out | egrep \(hello\|bye\)
    14: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hellobye
    28: 0000000000001224    54 FUNC    GLOBAL DEFAULT   16 _Z3byev
    43: 0000000000001182    54 FUNC    GLOBAL DEFAULT   16 hello
    44: 00000000000011ee    54 FUNC    GLOBAL DEFAULT   16 _Z5hellov
    45: 00000000000011b8    54 FUNC    GLOBAL DEFAULT   16 bye

has the extern "C" symbols linked from good.o and the C++ symbols linked from hellobye.o, but the symbols from good.o are redundant:

$ g++ main.o hellobye.o 
$ ./a.out
Hello
Goodbye

Similarly for the program:

$ gcc -c main.c
$ gcc main.o good.o hellobye.o -lstdc++
$ ./a.out
HI
BYE

$ readelf -sW a.out | egrep \(hello\|bye\)
    14: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hellobye
    28: 0000000000001224    54 FUNC    GLOBAL DEFAULT   16 _Z3byev
    43: 0000000000001182    54 FUNC    GLOBAL DEFAULT   16 hello
    44: 00000000000011ee    54 FUNC    GLOBAL DEFAULT   16 _Z5hellov
    45: 00000000000011b8    54 FUNC    GLOBAL DEFAULT   16 bye
    

where the symbols from hellobye.o are redundant.

You ask specifically about g++ -std=c++11 but you'll make the same findings with recent clang/clang++ or MSVC, default standard. Unlike g++, clang++ warns when a *.c file is input, but like g++ compiles it as C++ source. MSVC requires the option /TP to make it compile a *.c file as C++.


What you wanted to achieve was to write a shared library in C++ that has a C API. Here's how you would do that:

$ tail -n +1 hb.h hb 
==> hb.h <==
#ifndef HB_H
#define HB_H

#ifdef __cplusplus 
extern "C" {
#endif

extern void hello(void);
extern void bye(void);

#ifdef __cplusplus
}
#endif


#endif

==> hb <==
#include "hb.h"
#include <iostream>

extern "C" {

void hello(void) { std::cout << "Hello" << std::endl; }

void bye(void) { std::cout << "Goodbye" << std::endl; }

}

Create the library

$ g++ -fPIC -shared hb -o libhb.so

Link it with a C program:

$ cat main_1.c
#include "hb.h"

int main() {
    hello();
    bye();
    return 0;
}


$ gcc main_1.c -L. -lhb -Wl,-rpath='$ORIGIN'
$ ./a.out
Hello
Goodbye

Or link it with a C++ program, where the C API will be activated through header file:

$ g++ main_1.c -L. -lhb -Wl,-rpath='$ORIGIN'
$ ./a.out
Hello
Goodbye

When you #include "c_sample.h" in a C++ file, you declare a couple of functions with C++ linkage.

When you hand-declare functions in the global namespace, you get a conflict. A function with C linkage cannot have the same name as a function with C++ linkage in the same namespace.

When you hand-declare functions in the unnamed namespace, you don't get a conflict. You can declare a function in any namespace and function with the same name in an different namespace, regardless of linkage.

A C linkage function declared in any namespace is just an ordinary C function that can be implemented in a C source file and compiled by a C compiler (if no C++-specific types are involved).

There is nothing undefined or implementation-specific here, but the code is fragile and not very useful. The right thing to do is to add contitionally-compiled extern "C" right in the C header.

发布评论

评论列表(0)

  1. 暂无评论