3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

extern "C"をなんとなく付けている人へ

Last updated at Posted at 2024-06-11

こちらの記事を読んで気になったので、自分なりにCとC++が混在している場合の注意点や差異について調べました。
「undefined referenceって言われたけど、なんとなくextern "C"を付けたら上手くいった・・・」という方向け。
gccコマンドを叩ける前提で書いています。gccコマンドが不安な方はこちら

1. Cのみの場合

C言語だけで書かれたソースコードをコンパイルして実行してみます。

func1.c
#include <stdio.h>

void func1()
{
	printf("func1\n");
}
main.c
#include <stdio.h>

void func1(void);

int main()
{
	func1();
}
$ gcc -c *.c
$ gcc -o program *.o
$ gcc ./program
func1

上手くいきました。ここで、オブジェクトファイルのシンボルをnmコマンドを使って確認してみます。シンボルというのは、プログラム中で関数を特定するための名前だと思ってよいです。

$ nm func1.o
0000000000000000 T func1
                 U puts
$ nm main.o
                 U func1
0000000000000000 T main

ここで大事なのは、func1()関数のシンボルがfunc1で統一されていることです。func1.oも、main.oも、同じfunc1を使っているので、不整合が起きないのです。

2. C++のみの場合

同内容で、C++だけで書かれたソースコードをコンパイルして実行してみます。

func1.cpp
#include <stdio.h>

void func1()
{
	printf("func1\n");
}
main.cpp
#include <stdio.h>

void func1();

int main()
{
	func1();
}
$ gcc -c *.cpp
$ gcc -o program *.o
$ gcc ./program
func1

上手くいきました。ここで、オブジェクトファイルのシンボルをnmコマンドを使って確認してみます。

$ nm func1.o
0000000000000000 T _Z5func1v
                 U puts
$ nm main.o
                 U _Z5func1v
0000000000000000 T main

先ほどとは違い、シンボルが_Z5func1vになっていますが、統一はされていますので、不整合は起きません。ここでは、C++の場合はシンボルに関数名だけでなく色々な情報が付加される、ということがわかればよいです(マングリングと言います)。

3. CとC++が混在する場合

では、Cで関数定義をして、C++から呼び出す場合はどうなるでしょうか。
func1.cmain.cppをコンパイルして実行してみましょう。

$ gcc -c func1.c
$ gcc -c main.cpp
$ gcc -o program *.o
/usr/bin/ld: main.o: in function `main':
main.cpp:(.text+0x9): undefined reference to `func1()'
collect2: error: ld returned 1 exit status

ダメでした。シンボルを確認してみましょう。

$ nm func1.o
0000000000000000 T func1
                 U puts
$ nm main.o
                 U _Z5func1v
0000000000000000 T main

なぜダメかというと、C++ソースだったmain.o側は_Z5func1vを探しているのに、Cソースだったfunc1.o側はfunc1を提供しているからです。シンボルが一致していないので、関数が見つからなくなっているというわけです(リンケージが異なる)。
ではどうすればいいかというと、C++ソース上で、「func1はCリンケージですよ」と教えてあげればよいです。

main.cpp
#include <stdio.h>

//void func1(); // これはNG.
extern "C" void func1(); // Cで書かれた関数だと教えてあげる

int main()
{
	func1();
}
$ gcc -c func1.c
$ gcc -c main.cpp
$ gcc -o program *.o
$ ./program
func1

上手くいきました。シンボルも確認してみましょう。

$ nm func1.o
0000000000000000 T func1
                 U puts
$ nm main.o
                 U func1
0000000000000000 T main

main.oがシンボルfunc1を探すようになったので、無事にシンボルが一致してうまくいくようになりました。

4. C++にマングリングがある意味

Cでは、シンボル名が単なる関数名ですが、C++では関数名だけでなく色々と情報が付加されています。少々複雑ですが、C++ではこれにより関数のオーバーロード(関数名は同じでも引数が異なる)が可能になっています。

func1.cpp
#include <stdio.h>

void func1()
{
	printf("func1\n");
}

void func1(int num)
{
	printf("func1:%d\n", num);
}
main.cpp
#include <stdio.h>

void func1();
void func1(int num);

int main()
{
	func1();
	func1(10);
}
$ gcc -c func1.cpp
$ gcc -c main.cpp
$ gcc -o program *.o
$ ./program
func1
func1:10

シンボルを見てみましょう。

$ nm func1.o
000000000000001a T _Z5func1i
0000000000000000 T _Z5func1v
                 U printf
                 U puts
$ nm main.o
                 U _Z5func1i
                 U _Z5func1v
0000000000000000 T main

同じfunc1という関数名でも、シンボルは_Z5func1i_Z5func1vで異なっているため、正しくコンパイルできているのです。

また、C++では名前空間を分けることで、関数名も引数も全く同じ関数を使用することができます(名前空間を指定して呼び出す必要があります)。
以下は、C++側に同じ関数名の関数を2つ定義し、かつCリンケージにも同じ関数名の関数がある例です。乱暴なことをしているようですがシンボルを確認すると、別のシンボルがそれぞれ与えられており、問題ないことがわかります。

func_name1.cpp
#include <stdio.h>

namespace name1
{
	void func()
	{
		printf("func name1\n");
	}
}
func_name2.cpp
#include <stdio.h>

namespace name2
{
	void func()
	{
		printf("func name2\n");
	}
}
func.c
#include <stdio.h>

void func()
{
	printf("func C\n");
}
main.cpp
#include <stdio.h>

namespace name1 {
	void func();
}
namespace name2 {
	void func();
}
extern "C" void func();

int main()
{
	name1::func();
	name2::func();
	func();
}
$ gcc -c func_name1.cpp
$ gcc -c func_name2.cpp
$ gcc -c func.c
$ gcc -c main.cpp
$ gcc -o program *.o
$ ./program
func name1
func name2
func C
$ nm *.o

func.o:
0000000000000000 T func
                 U puts

func_name1.o:
0000000000000000 T _ZN5name14funcEv
                 U puts

func_name2.o:
0000000000000000 T _ZN5name24funcEv
                 U puts

main.o:
                 U _ZN5name14funcEv
                 U _ZN5name24funcEv
                 U func
0000000000000000 T main
3
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?