Linux C/C + + learning 3: the role of extern "C"

Keywords: C++ Linux Cpp

1. Overview of extern "C"

The main function of extern "C" is to implement other C codes in C++ code. The format is as follows: cplusplus is the default macro defined by the C + + compiler, and the code block part is the C language implementation code. Extern "C" instructs the compiler to compile this part of the code in C language instead of C + +.

#ifndef __cplusplus
    extern "C" {
#endif
	/* ... code block ... */
#ifndef __cplusplus
    }
#endif

2. Instructions for extern "C"

2.1. C ase of C + + calling C

See the source code example in Appendix A, where the use of extern "C" is detailed in the notes in demo2.h. Perform the following operations in the current path of the source code structure shown in A.1.

  • Compile C to realize the source code demo2 and generate the target file demo2.o:
    gcc -Wall ./demo_c/demo2.c -c -o ./build/demo2.o
  • Compile C + + to realize the source code demo1 and generate the target file demo1.o:
    g++ -Wall ./demo_cpp/demo1.cpp -c -o ./build/demo1.o -I./demo_c
  • Generate the final executable demo and execute:
    g++ -Wall ./build/*.o -o ./build/demo
root@ubuntu:/opt/extern_tmp# gcc -Wall ./demo_c/demo2.c -c -o ./build/demo2.o
root@ubuntu:/opt/extern_tmp# g++ -Wall ./demo_cpp/demo1.cpp -c -o ./build/demo1.o -I./demo_c
root@ubuntu:/opt/extern_tmp# g++ -Wall ./build/*.o -o ./build/demo
root@ubuntu:/opt/extern_tmp# ls ./build
demo  demo1.o  demo2.o
root@ubuntu:/opt/extern_tmp# ./build/demo 
a+b=2
a-b=0
root@ubuntu:/opt/extern_tmp# 

2.2. C ase of C calling C + +

When calling the C++ Library in the C source code, you can use the two way to encapsulate the intermediate interface library. See the source code example in Appendix B, where the use of extern "C" is detailed in the notes in mid.cpp. Perform the following operations in the current path of the source code structure shown in B.1.

  • Compile C + + to realize the source code demo2 and generate the shared library libdemo2.so:
    g++ -Wall -shared -fPIC ./demo_cpp/demo2.cpp -o ./build/libdemo2.so
  • Compile the intermediate interface files mid.cpp and libdemo2.so, and generate the intermediate interface library libdemo2 through secondary encapsulation_ mid.so:
    g++ -Wall -shared -fPIC ./build/libdemo2.so ./demo_c/mid.cpp -o ./build/libdemo2_mid.so -I./demo_cpp/
  • Compile C to realize the source code demo1 and call libdemo2_ The shared libraries of mid.so and libdemo2.so generate the executable demo:
    gcc -Wall ./demo_c/demo1.c -o ./build/demo -L./build/ -ldemo2_mid -ldemo2
  • Execute the demo executable and find libdemo2 not found_ Mid.so, add the shared library path (see another article by the author) Static library and dynamic library ), and execute again:
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/extern_tmp1/build/
    echo $LD_LIBRARY_PATH
    ./build/demo
root@ubuntu:/opt/extern_tmp1# g++ -Wall -shared -fPIC ./demo_cpp/demo2.cpp -o ./build/libdemo2.so
root@ubuntu:/opt/extern_tmp1# g++ -Wall -shared -fPIC ./build/libdemo2.so ./demo_c/mid.cpp -o ./build/libdemo2_mid.so -I./demo_cpp/
root@ubuntu:/opt/extern_tmp1# gcc -Wall ./demo_c/demo1.c -o ./build/demo -L./build/ -ldemo2_mid -ldemo2
root@ubuntu:/opt/extern_tmp1# ls ./build/
demo  libdemo2_mid.so  libdemo2.so
root@ubuntu:/opt/extern_tmp1# ./build/demo
./build/demo: error while loading shared libraries: libdemo2_mid.so: cannot open shared object file: No such file or directory
root@ubuntu:/opt/extern_tmp1# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/extern_tmp1/build/
root@ubuntu:/opt/extern_tmp1# echo $LD_LIBRARY_PATH
:/opt/extern_tmp1/build/
root@ubuntu:/opt/extern_tmp1# ./build/demo
c++ implements a+b
a+b=2
root@ubuntu:/opt/extern_tmp1# 

2.3. Key points for using extern "C"

  • Single statement form:
extern "C" int add(int a, int b);
  • The form of compound statement is equivalent to adding extern "C" to the declarations in the compound statement:
extern "C"
{
      int add(int a, int b);
      double sub(double a, float b);
}
  • Contains the header file form, which is equivalent to adding extern "C" to all declarations in the header file:
extern "C"
{
    #include <demo2.h>
} 
  • Other instructions:
    (1) extern "C" cannot be added inside a function.
    (2) If a function has multiple declarations, it can all be added with extern "C", or it can only appear in the first declaration, and the subsequent declarations will accept the rule of the first link indicator.
    (3) In addition to extern "C", there are extern "FORTRAN" and so on.

3. In depth analysis of extern "C"

3.1. What happens without using extern "C"?

C and C + + compilers do not compile functions exactly the same.

  • C + + supports function overloading, so the compiler will add the parameter type of the function to the compiled code during function compilation, that is, the compiled function name is generally named after the original function name and formal parameter type
  • C language does not support function overloading, so the function of C language code will not be compiled with the parameter type of the function, that is, the original function name

In the link phase, functions are found by function names. The function of extern "C" is to force the code contained in it to be compiled in C language, that is, the compiled function name is the original function name. Otherwise, common "undefined reference to..." errors will appear.

Next, we take the source code in Appendix A as an example to further illustrate.

  1. Modify the demo1.h source code in Appendix A and comment out the code related to extern "C", as shown below.
// File: demo1.h
#ifndef __DEMO1_H__
#define __DEMO1_H__

//#ifdef __cplusplus 			// __ Cplusplus is the default macro defined by the c + + compiler
//    extern "C" { 			//  extern "C" tells the compiler that this part of the code is compiled in c language
//#endif
#include "demo2.h" 			//  Call the demo2 header file implemented by c and compile it in c language in extern "C"
//#ifdef __cplusplus
//    }
//#endif

double sub(double a, float b);	// demo1 has its own function, which is not in extern "C", and is compiled in c + +

#endif
  1. As described in Section 2.1., recompile and generate the target files demo1.o and demo2.o and check them. It can be found that the add function name in demo1.o compiled by C + + is actually _Z3addii, where ii represents two int type parameters; while the add function name in demo2.o compiled by C is the original function name add.
root@ubuntu:/opt/extern_tmp# gcc -Wall ./demo_c/demo2.c -c -o ./build/demo2.o
root@ubuntu:/opt/extern_tmp# g++ -Wall ./demo_cpp/demo1.cpp -c -o ./build/demo1.o -I./demo_c
root@ubuntu:/opt/extern_tmp# nm -A ./build/demo1.o 
./build/demo1.o:                 U __cxa_atexit
./build/demo1.o:                 U __dso_handle
./build/demo1.o:                 U _GLOBAL_OFFSET_TABLE_
./build/demo1.o:0000000000000113 t _GLOBAL__sub_I_main
./build/demo1.o:0000000000000000 T main
./build/demo1.o:                 U _Z3addii 
./build/demo1.o:00000000000000a4 T _Z3subdf
./build/demo1.o:00000000000000c6 t _Z41__static_initialization_and_destruction_0ii
./build/demo1.o:                 U _ZNSolsEd
./build/demo1.o:                 U _ZNSolsEi
./build/demo1.o:                 U _ZNSt8ios_base4InitC1Ev
./build/demo1.o:                 U _ZNSt8ios_base4InitD1Ev
./build/demo1.o:                 U _ZSt4cout
./build/demo1.o:0000000000000000 r _ZStL19piecewise_construct
./build/demo1.o:0000000000000000 b _ZStL8__ioinit
./build/demo1.o:                 U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
root@ubuntu:/opt/extern_tmp# 
root@ubuntu:/opt/extern_tmp# nm -A ./build/demo2.o 
./build/demo2.o:0000000000000000 T add
root@ubuntu:/opt/extern_tmp# 
  1. Continue the link operation as described in Section 2.1. And the error is as follows. The add function name compiled in demo1.o is _z3addiiand it is in U(Undefined) state. The compiler will look for _z3addiiin demo2.o. however, the add function name in demo2.o is add instead of _Z3addii, so it is not found and an error is reported. If extern "C" is used , then _z3addiiin demo1.o will become add, that is, if the function is forcibly compiled in C language, the link will succeed, and interested readers can verify it by themselves.
root@ubuntu:/opt/extern_tmp# g++ -Wall ./build/*.o -o ./build/demo
/usr/bin/ld: ./build/demo1.o: in function `main':
demo1.cpp:(.text+0x35): undefined reference to `add(int, int)'
collect2: error: ld returned 1 exit status
root@ubuntu:/opt/extern_tmp#

3.2. Dual meaning of extern "C"

  1. The function or variable qualified by extern "C" is of type extern
    (1) extern is a keyword in C/C + + language that indicates the scope of functions and global variables. This keyword tells the compiler that the declared functions and variables can be used in this module or other modules; it should be noted that the statement extern int a is only a declaration of a variable, not defining variable a or allocating space for A. variable a is used as a variable in all modules Global variables can only be defined once, otherwise an error will occur.
    (2) In general, in the header file of a module, the functions and global variables referenced by this module to other modules are declared in keyword extern. For example, if module B has to refer to global variables and functions defined in module A, it only needs header files containing module A. In this way, when module functions are invoked in module A, module B will not find the function in compile stage. No error will be reported. It will find the function from the object code compiled by module a in the link phase.
    (3) The keyword corresponding to extern is static. Static indicates that variables or functions can only be used in this module. Therefore, variables or functions modified by static cannot be modified by extern "C".
  2. Variables and functions modified by extern "C" are compiled and linked in the way of C language
    As mentioned above, C + + supports function overloading, but C language does not. Therefore, the name of the function in the symbol table after being compiled by C + + is different from that of C language; the function after C + + compilation needs to add the type of parameters to uniquely calibrate the overloaded function, and the addition of extern "C" is to indicate to the compiler that this code is compiled in the way of C language.

3. References

  1. Detailed explanation of the function of extern "C"
  2. extern c action

Appendix a example of C + + calling C source code

A.1 source code structure

root@ubuntu:/opt/extern_tmp# tree ./
./
├── build
│   ├── demo				// Final generated executable
│   ├── demo1.o				// gcc compiled c source code object file
│   └── demo2.o				// c + + source code object file compiled by g + +
├── demo_c					// c source code
│   ├── demo2.c
│   └── demo2.h
└── demo_cpp				// c + + source code, call c source code
    ├── demo1.cpp
    └── demo1.h
3 directories, 7 files
root@ubuntu:/opt/extern_tmp#

A.2 source code implementation

  • demo1.cpp source code
// File: demo1.cpp
#include <iostream>
using namespace std;
#include "demo1.h"

int main(int argc, char *argv[])
{
    cout << "a+b=" << add(1,1) << "\n";		// Call the function add in demo2 implemented in c
    cout << "a-b=" << sub(1,1) << "\n";		// Call demo1 own function sub implemented in c + +
    return 0;
}

double sub(double a, float b)
{
    return (a-b);
}
  • demo1.h source code
// File: demo1.h
#ifndef __DEMO1_H__
#define __DEMO1_H__

#ifdef __cplusplus 			// __ Cplusplus is the default macro defined by the c + + compiler
    extern "C" {			// extern "C" tells the compiler that this part of the code is compiled in c language
#endif
#include "demo2.h" 			//  Call the demo2 header file implemented by c and compile it in c language in extern "C"
#ifdef __cplusplus
    }
#endif

double sub(double a, float b);	// demo1 has its own function, which is not in extern "C", and is compiled in c + +

#endif
  • demo2.c source code
// File: demo2.c
#include "demo2.h"
int add(int a, int b)
{
    return (a+b);
}
  • demo2.h source code
// File: demo2.h
#ifndef __DEMO2_H__
#define __DEMO2_H__

int add(int a, int b);

#endif

Appendix B example of C calling C + + source code

B.1 source code structure

root@ubuntu:/opt/extern_tmp1# tree ./
./
├── build
│   ├── demo					// gcc compiles demo1.c and libdemo2_ Executable files generated by mid.so and libdemo2.so
│   ├── libdemo2_mid.so			// g + + compiles the intermediate interface library generated by libdemo2.so and mid.cpp
│   └── libdemo2.so				// Dynamic library generated by g + + compiling c + + source code
├── demo_c
│   ├── demo1.c					// c source code, call c + + source code through intermediate interface library
│   └── mid.cpp					// Intermediate c + + source code is used for secondary encapsulation of c + + library to generate intermediate interface library
└── demo_cpp					// c + + source code, generate dynamic library
    ├── demo2.cpp
    └── demo2.h
3 directories, 7 files
root@ubuntu:/opt/extern_tmp1# 

B.2 source code implementation

  • demo1.cpp source code
// File: demo1.c
#include <stdio.h>

int m_add(int a, int b);

int main(int argc, char *argv[])
{
    printf("a+b=%d\n", m_add(1,1));		// Call m in the intermediate interface library_ Add function
    return 0;
}
  • mid.cpp source code
// File: mid.cpp
#include <iostream>
#include "demo2.h" 				//  Contains the add function declaration of the original c + + implementation, which is not in extern "C", but is still compiled in c + +

#ifdef __cplusplus
    extern "C" {				// extern "C" tells the compiler that this part of the code is compiled in c language
#endif

int m_add(int a, int b)			// Secondary packaging into m_ The add function, in extern "C", will be compiled in c language and can be called by c source code
{
    return add(a, b);
}

#ifdef __cplusplus
    }
#endif
  • demo2.cpp source code
// File: demo2.cpp
#include <iostream>

int add(int a, int b)
{
    std::cout << "c++ implements a+b" << "\n";
    return (a+b);
}
  • demo2.cpp source code
// File: demo2.h
#ifndef __DEMO2_H__
#define __DEMO2_H__

int add(int a, int b);

#endif

Posted by kbaker on Wed, 24 Nov 2021 07:59:53 -0800