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 | Show 2 more comments2 Answers
Reset to default 1How 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.
#include c_sample.h"
within anextern "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 addsextern "C"
decorations. – Peter Commented Mar 30 at 0:27undefined reference to `hello()'
in the second option. godbolt./z/zf6ahajqq – 3CxEZiVlQ Commented Mar 30 at 0:33extern
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