Overview
The C++ compiler may modify the names of symbols when compiling C++ code:int NonExistentFunction(int a, int b);
int main()
{
int x = NonExistentFunction(1, 2);
return 0;
}
This is the error message from the linker from GNU g++:
and Microsoft's linker says this:/tmp/ccl1aw52.o: In function `main': main.cpp:(.text+0x13): undefined reference to `NonExistentFunction(int, int)' collect2: error: ld returned 1 exit status
Pretty obvious what the problem is, huh?error LNK2001: unresolved external symbol "int __cdecl NonExistentFunction(int,int)" (?NonExistentFunction@@YAHHH@Z)
To undecorate the name, you can use a tool that comes with Visual Studio called undname:
and this is the output:undname ?NonExistentFunction@@YAHHH@Z
You can also use the dumpbin program from Microsoft to show all of the symbols in the object file:Microsoft (R) C++ Name Undecorator Copyright (C) Microsoft Corporation. All rights reserved. Undecoration of :- "?NonExistentFunction@@YAHHH@Z" is :- "int __cdecl NonExistentFunction(int,int)"
Output:dumpbin /symbols main.obj
To see the symbols from g++, use objdumpMicrosoft (R) COFF/PE Dumper Version 14.00.23026.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file main.obj File Type: COFF OBJECT COFF SYMBOL TABLE 000 010559F2 ABS notype Static | @comp.id 001 80000190 ABS notype Static | @feat.00 002 00000000 SECT1 notype Static | .drectve Section length 2F, #relocs 0, #linenums 0, checksum 0 004 00000000 SECT2 notype Static | .debug$S Section length 90, #relocs 0, #linenums 0, checksum 0 006 00000000 SECT3 notype Static | .text$mn Section length 1E, #relocs 1, #linenums 0, checksum 63728334 008 00000000 UNDEF notype () External | ?NonExistentFunction@@YAHHH@Z (int __cdecl NonExistentFunction(int,int)) 009 00000000 SECT3 notype () External | main 00A 00000000 SECT3 notype Label | $LN3 00B 00000000 SECT4 notype Static | .xdata Section length 8, #relocs 0, #linenums 0, checksum 37887F31 00D 00000000 SECT4 notype Static | $unwind$main 00E 00000000 SECT5 notype Static | .pdata Section length C, #relocs 3, #linenums 0, checksum 69312319 010 00000000 SECT5 notype Static | $pdata$main String Table Size = 0x3B bytes Summary 90 .debug$S 2F .drectve C .pdata 1E .text$mn 8 .xdata
Output:objdump -t main.o
You can also use the nm tool:main.o: file format elf64-x86-64 SYMBOL TABLE: 0000000000000000 l df *ABS* 0000000000000000 main.cpp 0000000000000000 l d .text 0000000000000000 .text 0000000000000000 l d .data 0000000000000000 .data 0000000000000000 l d .bss 0000000000000000 .bss 0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack 0000000000000000 l d .eh_frame 0000000000000000 .eh_frame 0000000000000000 l d .comment 0000000000000000 .comment 0000000000000000 g F .text 0000000000000021 main 0000000000000000 *UND* 0000000000000000 _Z19NonExistentFunctionii
Output:nm main.o
To undecorate the name, there is a tool called c++filt:0000000000000000 T main U _Z19NonExistentFunctionii
Output:c++filt -t _Z19NonExistentFunctionii
You can do an entire object file by using c++filt as a filterNonExistentFunction(int, int)
Output:nm main.o | c++filt
0000000000000000 T main U NonExistentFunction(int, int
The previous error was generated on purpose. Let's see how we might get it "not on purpose."
This is a C++ program that compiles and links just fine as expected: (main2.cpp)
#include <iostream>
void cpp_swap_p(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int x = 1, y = 2;
cpp_swap_p(&x, &y);
std::cout << "x = " << x << ", y = " << y << std::endl;
return 0;
}
Now, suppose we want to use the swap function in a C program. We've created a file (cpp_swap.cpp)
that contains the function:
And we've got a C file: (main.c)void cpp_swap_p(int *a, int *b) { int temp = *a; *a = *b; *b = temp; }
#include <stdio.h>
// prototype (could be in a .h file)
void cpp_swap_p(int *a, int *b);
int main(void)
{
int a = 1, b = 2;
cpp_swap_p(&a, &b);
printf("a = %i, b = %i\n", a, b);
return 0;
}
Now we compile each file separately:
Then we link them together:gcc -c main.c -o main.o g++ -c cpp_swap.cpp -o cpp_swap.o
And we get a linker error:gcc main.o cpp_swap.o
We also get the same error if we try to link with g++. Given the example name mangling above, it should be clear what the problem is.main.o: In function `main': main.c:(.text+0x25): undefined reference to `cpp_swap_p' collect2: error: ld returned 1 exit status
cpp_swap_p
_Z10cpp_swap_pPiS_
We can verify the actual name with c++filt0000000000000000 T _Z10cpp_swap_pPiS_
Output:c++filt _Z10cpp_swap_pPiS_
We need to modify the C++ code so that C code can access it. We simply use the extern keyword and specify the language as a string:cpp_swap_p(int*, int*)
To see that it works, run nm on the object file again:extern "C" void cpp_swap_p(int *a, int *b) { int temp = *a; *a = *b; *b = temp; }
The cpp_swap_p function is now accessible from C and will link. Incidentally, you can put any language in the double quotes after the extern keyword as long as it's C. Actually, you can also put C++, but that's the default. This will mangle the name:0000000000000000 T cpp_swap_p
extern "C++" void cpp_swap_p(int *a, int *b)
Another thing that should be clear: Each C++ compiler has it's own way of mangling names. This means that you can't compile some files with one compiler and other files with a different compiler and expect them to work together. They will only work if the compilers each output compatible object code.
Overloading example
Given this code:void fn();
void fn(int);
void fn(double);
void fn(int, double);
void fn(double, int);
int fn(int, int);
int main()
{
fn();
fn(1);
fn(1.0);
fn(1, 1.0);
fn(1.0, 1);
fn(1, 1);
return 0;
}
These are the errors that are emitted by the linker (Microsoft):
error LNK2001: unresolved external symbol "int __cdecl fn(int,int)" (?fn@@YAHHH@Z) error LNK2001: unresolved external symbol "void __cdecl fn(double,int)" (?fn@@YAXNH@Z) error LNK2001: unresolved external symbol "void __cdecl fn(int,double)" (?fn@@YAXHN@Z) error LNK2001: unresolved external symbol "void __cdecl fn(double)" (?fn@@YAXN@Z) error LNK2001: unresolved external symbol "void __cdecl fn(int)" (?fn@@YAXH@Z) error LNK2001: unresolved external symbol "void __cdecl fn(void)" (?fn@@YAXXZ)
Borland mangles the functions like this:
Function names Mangled names void fn(void) void fn(int) void fn(double) void fn(int, double) void fn(double, int) int fn(int, int) @fn$qv @fn$qi @fn$qd @fn$qid @fn$qdi @fn$qii
GNU g++ mangles the functions like this (Clang is the same):
Function names Mangled names void fn(void) void fn(int) void fn(double) void fn(int, double) void fn(double, int) int fn(int, int) __Z2fnv __Z2fni __Z2fnd __Z2fnid __Z2fndi __Z2fnii
More uses of extern:
This code would presumably be compiled with a C++ compiler:
extern "C" void cpp_swap_p(int *a, int *b);
extern "C" void cpp_swap_r(int &a, int &b);
void cpp_swap_p(int *a, int *b) // using pointers
{
int temp = *a;
*a = *b;
*b = temp;
}
void cpp_swap_r(int &a, int &b) // using references
{
int temp = a;
a = b;
b = temp;
}
Or, we could tag a block of functions:
extern "C" { void cpp_swap_p(int *a, int *b); // using pointers void cpp_swap_r(int &a, int &b); // using references }
Wrapping C header files in C++
Note the C code using pointers instead of references:
#include <stdio.h>
// prototypes (could be in a .h file)
void cpp_swap_p(int *a, int *b); // using pointers
void cpp_swap_r(int *a, int *b); // using pointers (no references in C)
int main()
{
int a = 1, b = 2;
cpp_swap_p(&a, &b);
printf("a = %i, b = %i\n", a, b);
cpp_swap_r(&a, &b);
printf("a = %i, b = %i\n", a, b);
return 0;
}
Outupt:
a = 2, b = 1
a = 1, b = 2
Using the STL in C
Who knew?
Yes, you can do that. These examples are somewhat contrived, but you should be able to see the big picture. Suppose you want to sort an array of integers using std::sort from the STL. (For now, ignore the fact that the C standard library already has a sort function...)
Here's our C-compatible sort function written in C++ that uses the STL: (sort.cpp)
A C driver: (driver.c)#include <algorithm> // sort extern "C" void stl_sort(int *array, int size, bool (*compare)(int a, int b)) { std::sort(array, array + size, compare); }
#include <stdio.h>
#include <stdlib.h>
/* Prototype for STL sort in C++ code */
void stl_sort(int *array, int size, int (*compare)(int a, int b));
/* For sorting with greater than */
int greater(int a, int b)
{
return a > b;
}
/* For sorting with less than */
int less(int a, int b)
{
return a < b;
}
/* Calculate sum of digits */
int sumhelper(int a)
{
int sum = 0;
while (a)
{
sum += a % 10;
a /= 10;
}
return sum;
}
/* For sorting by sum of digits (greatest) */
int sumdigits(int a, int b)
{
int suma = sumhelper(a);
int sumb = sumhelper(b);
return suma > sumb;
}
int RandomInt(int low, int high)
{
int number = rand() % (high - low + 1) + low;
return number;
}
void dump(int *array, int size)
{
int i;
for (i = 0; i < size; i++)
printf("%i ", array[i]);
printf("\n");
}
int main(void)
{
int size = 50;
int *array = malloc(size * sizeof(int));
int i;
/* Populate array with random integers */
for (i = 0; i < size; i++)
array[i] = RandomInt(1, 500);
/* Print out unsorted array */
printf("Unsorted:\n");
dump(array, size);
/* Sort the array smallest to largest */
stl_sort(array, size, less);
/* Print out sorted array */
printf("Sorted small to large:\n");
dump(array, size);
/* Sort the array largest to smallest */
stl_sort(array, size, greater);
/* Print out sorted array */
printf("Sorted large to small:\n");
dump(array, size);
/* Sort the array by sum of digits */
stl_sort(array, size, sumdigits);
/* Print out sorted array */
printf("Sorted by sum of digits:\n");
dump(array, size);
free(array);
return 0;
}
Compile the files and link them:
Output:gcc -c driver.c -Wall -Wextra -ansi -pedantic -O2 g++ -c sort.cpp -Wall -Wextra -ansi -pedantic -O2 gcc driver.o sort.o -o sort
Unsorted: 384 387 278 416 294 336 387 493 150 422 363 28 191 60 264 427 41 427 173 237 212 369 68 430 283 31 363 124 68 136 430 303 23 59 70 168 394 457 12 43 230 374 422 420 285 38 199 325 316 371 Sorted small to large: 12 23 28 31 38 41 43 59 60 68 68 70 124 136 150 168 173 191 199 212 230 237 264 278 283 285 294 303 316 325 336 363 363 369 371 374 384 387 387 394 416 420 422 422 427 427 430 430 457 493 Sorted large to small: 493 457 430 430 427 427 422 422 420 416 394 387 387 384 374 371 369 363 363 336 325 316 303 294 285 283 278 264 237 230 212 199 191 173 168 150 136 124 70 68 68 60 59 43 41 38 31 28 23 12 Sorted by sum of digits: 199 369 387 387 278 493 394 457 168 285 384 294 68 68 59 374 283 427 427 363 336 363 237 264 173 38 191 416 371 28 325 136 316 422 422 43 430 124 70 430 420 60 150 303 41 212 230 23 31 12
Using an Object-Oriented SpellChecker in C
Here's a more real-world example of calling a method on an C++ object.FileStrings.h (Text) This file is the C++ interface that is used by the C++ code.
SpellChecker.h (Text) This is the C interface to the C++ code that will be called by C code.
SpellChecker.cpp (Text) This is the C++ implementation of the C interface.
spellcheck.c (Text) This is the C code that will call the C++ code via the C interface.
Compiling the C and C++ files, then linking: (You have to provide FileStrings.cpp)
The output from running the executable should look like this:gcc -c spellcheck.c -Wall -Wextra -ansi -pedantic -O2 -g g++ -c FileStrings.cpp -Wall -Wextra -ansi -pedantic -O2 -g g++ -c SpellChecker.cpp -Wall -Wextra -ansi -pedantic -O2 -g g++ spellcheck.o SpellChecker.o FileStrings.o -o spell
If you don't have FileStrings.cpp, you can just use one of these object files that was compiled for the appropriate platform. You'll want to rename it to simply FileStrings.o to use with the command lines above.Four is correct. SCORE is correct. and is correct. sevn is incorrect. years is correct. ago is correct. ar is correct. fawthers is incorrect. brought is correct. foarth is incorrect. on is correct. this is correct. contnent is incorrect. a is correct. gnu is correct. nashun is incorrect.
FileStrings.o-linux64 - Compiled with 64-bit g++ (v. 5.3.0) on Linux.
FileStrings.o-cygwin64 - Compiled with 64-bit Cygwin g++ (v. 5.4.0) on Windows.
FileStrings.o-vc64 - Compiled with Microsoft's 64-bit compiler (v. 19.0, VS 14.0) on Windows.
FileStrings.o-macos64 - Compiled with 64-bit clang++ (Apple LLVM v. 8.0.0) on MacOS.
Example to Try on Your Own
The goal is to "fix" this existing code so that it compiles (without warnings) and produces this output:
The output indicates that after the C code prints its information, it calls the cpp_stuff function to do its work.In C: i = 5, d = 5 In CPP: i = 5, d = 5
This is the start of CFile.c:
int main()
{
int i = sqrt(25.0);
double d = sqrt(25.0);
printf("In C: i = %i, d = %g\n", i, d);
return 0;
}
This is the start of CPPFile.cpp:
void cpp_stuff()
{
int i = sqrt(25.0);
double d = sqrt(25.0);
printf("In CPP: i = %i, d = %g\n", i, d);
}
Commands to compile and link:
Solutionsgcc -c CFile.c -Wall -Wextra -ansi -pedantic -O2 g++ -c CPPFile.cpp -Wall -Wextra -ansi -pedantic -O2 gcc CFile.o CPPFile.o -o mix