Skip to Content
Course content

145: extern C and Linkage

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Alright, let's look at a situation I ran into a few years ago when I was integrating a legacy C-based hardware driver into a modern C++ project. I had a perfectly functional C file that handled a specific checksum calculation for a network packet, and I just wanted to call it from my C++ application. It seemed trivial, but the linker had other plans.

The Linker's Confusion

I started with a simple C file, checksum.c, which contained a function to calculate a basic XOR checksum. No fancy C++ here, just raw C:

// checksum.c
unsigned char calculate_checksum(const unsigned char* data, int len) {
    unsigned char checksum = 0;
    for (int i = 0; i < len; i++) {
        checksum ^= data[i];
    }
    return checksum;
}

Then, in my main.cpp, I declared the function and tried to use it:

// main.cpp
#include <iostream>

unsigned char calculate_checksum(const unsigned char* data, int len);

int main() {
    unsigned char buffer[] = {0x10, 0x20, 0x30};
    std::cout < "Checksum: " < (int)calculate_checksum(buffer, 3) < std::endl;
    return 0;
}

I compiled them separately and tried to link them together. I expected a binary, but instead, I got a wall of text from the linker. Specifically, this line:
undefined reference to `calculate_checksum(unsigned char const*, int)`.

Wait, what? I literally just wrote that function. I checked the spelling, I checked the types—everything matched. I started wondering if the compiler was playing tricks on me, so I decided to peek at the object files using nm to see what names were actually being exported.

In the C object file (checksum.o), I saw:
T calculate_checksum

But in the C++ object file (main.o), the linker was looking for something that looked like:
U _Z18calculate_checksumPKcm

The Mystery of Name Mangling

This is where the "Aha!" moment happened. C++ supports function overloading—the ability to have multiple functions with the same name but different arguments. To make this work, the C++ compiler can't just name the function calculate_checksum in the binary; it has to "mangle" the name to include the parameter types so the linker knows exactly which version of the function to call.

C, however, doesn't have overloading. A function named calculate_checksum is just calculate_checksum in the object file. Period.

My C++ code was looking for the mangled version (_Z18...), but my C library only provided the unmangled version. They were speaking two different languages at the linkage level.

Telling C++ to Behave

I needed a way to tell the C++ compiler: "Hey, I know you usually mangle names, but for this specific function, please use the C-style naming convention."

That's where extern "C" comes in. I went back to main.cpp and adjusted the declaration:

// main.cpp
#include <iostream>

extern "C" {
    unsigned char calculate_checksum(const unsigned char* data, int len);
}

int main() {
    // ... same as before ...
}

I wrapped the declaration in an extern "C" block. This tells the C++ compiler to disable name mangling for anything inside those braces. I re-compiled, and suddenly, the linker found calculate_checksum without any complaints. It's a simple switch, but it's the bridge that allows C++ to talk to C.

Making the Header Universal

Now, in a real project, you wouldn't manually type the declaration in main.cpp. You'd use a header file. But here's the catch: if I put extern "C" inside checksum.h, and then I try to include that header in a .c file, the C compiler will freak out. C doesn't know what extern "C" is—it's a C++ keyword.

To solve this, we use the __cplusplus macro, which is only defined when the compiler is in C++ mode. Here is how I finally structured the header to work for both languages:

// checksum.h
#ifndef CHECKSUM_H
#define CHECKSUM_H

#ifdef __cplusplus
extern "C" {
#endif

unsigned char calculate_checksum(const unsigned char* data, int len);

#ifdef __cplusplus
}
#endif

#endif

It looks a bit clunky, but it's the industry standard. If a C compiler reads this, it ignores everything inside the #ifdef __cplusplus blocks. If a C++ compiler reads it, it wraps the function in extern "C". Now my legacy C code and my new C++ code can coexist peacefully.




📋 Practical Task

Exercise: Integrating a C-Based Math Utility

You have been given a legacy C file, fast_math.c, that implements a specialized square root approximation. Your task is to create a C++ wrapper that allows a C++ program to use this function without causing linker errors.

Provided File: fast_math.c

double fast_sqrt(double x) {
    // A simplified approximation for the sake of the exercise
    return x / 2.0; 
}

Your Requirements:

  • Create a header file fast_math.h that declares fast_sqrt. It must be written such that it can be included by both C and C++ compilers using the __cplusplus macro.
  • Create a main.cpp file that includes fast_math.h, calls fast_sqrt(16.0), and prints the result to the console.
  • Ensure that when you compile and link the two files (e.g., g++ main.cpp fast_math.c -o program), there are no "undefined reference" errors.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.