概念:声明如何链接到适当的定义
Concept: How are declarations linked to apropriate definitions
头文件或任何前向声明究竟如何知道它指的是哪个定义?
我知道 .cpp 文件是独立编译的,我们需要一个头文件或前向声明来访问另一个 .cpp 文件的成员。但是当我们声明一个成员时,我们并没有明确地告诉编译器从哪里得到定义。
这是我能想到的一个案例:
假设我有两个 cpp 文件 'one.cpp' 和 'two.cpp'。
'one.cpp' 和 'two.cpp' 都有一个成员 'int func(int x)',它们具有不同的实现(但具有确切的名称和格式)。
如果我们在这两个文件之外的某处有这个函数的头文件或声明,编译器如何知道采用哪个定义?
解析每个声明的定义由链接器执行。每个声明必须有一个唯一的定义。虽然可以多次声明一个函数,但每个函数必须在所有要链接的编译单元中只定义一次。如果具有相同签名的函数有多个定义,则链接器将抛出错误并拒绝完成可执行文件的构建。
我建议您创建您描述的示例文件,并尝试将它们构建为单个可执行文件。您将看到我在此处描述的错误。
If we have a header file or declaration of this function somewhere outside these two files, how does the compiler know which definition to take?
它是linker,它将一个或多个目标文件或库作为输入,并将它们组合起来生成一个可执行文件。这样做时,它解析对外部符号的引用,即它从其他“.obj”文件和外部库中查找所有外部函数和全局变量的定义,将最终地址分配给 procedures/functions 和变量,并修改代码和反映新地址的数据。
让我们考虑一下您提到的例子:
Say I have two cpp files 'one.cpp' and 'two.cpp'. Both 'one.cpp' and 'two.cpp' have a member 'int func(int x)' that have different implementations....
说,one.cpp
:
int func(int x)
{
return x+1;
}
和two.cpp
:
int func(int x)
{
return x+2;
}
和声明 func()
函数的头文件,比如 myinc.h
:
int func(int x);
和正在调用 func()
的 main()
,比如 main.cpp
:
#include <iostream>
#include <myinc.h>
int main()
{
int res;
res = func(10);
std::cout << res << std::endl;
return 0;
}
我可以创建 main.cpp
的目标文件,因为目标文件可以引用未定义的符号。
>g++ -I . -c main.cpp
现在让我们使用nm
命令检查对象文件main.o
,输出是:
Symbols from main.o:
Name Value Class Type Size Line Section
_GLOBAL__I_main |0000000000000078| t | FUNC|0000000000000015| |.text
_Z41__static_initialization_and_destruction_0ii|0000000000000038| t | FUNC|0000000000000040| |.text
_Z4funci | | U | NOTYPE| | |*UND*
.......
.......<SNIP>
func()
函数Class为U,表示未定义。
编译器不会介意找不到特定函数的定义,它只会假设该函数是在另一个文件中定义的。
另一方面,链接器可能会查看多个文件并尝试查找对未提及函数的引用。
因此,当我们尝试从目标文件 one.o
、two.o
和 main.o
创建可执行文件时:
>g++ two.o one.o main.o -o outexe
one.o: In function `func(int)':
one.cpp:(.text+0x0): multiple definition of `func(int)'
two.o:two.cpp:(.text+0x0): first defined here
collect2: ld returned 1 exit status
在这里您可以看到链接器为 func()
抛出 multiple definition
错误,因为它找到了 func()
.
的两个定义
c++中有一个one definition rule,表示:
In the entire program, an object or non-inline function cannot have more than one definition; if an object or function is used, it must have exactly one definition. You can declare an object or function that is never used, in which case you don't have to provide a definition. In no event can there be more than one definition.
因此,该程序违反了 ODR,因为它包含同一函数声明的两个定义。
如果我们不向链接器提供 one.o
或 two.o
目标文件,意味着,如果我们只提供 func()
的一个定义,它将生成 exe:
>g++ one.o main.o -o outexe
如果我们检查 outexe
,我们得到:
Symbols from outexe:
Name Value Class Type Size Line Section
.....<SNIP>
_Z41__static_initialization_and_destruction_0ii|000000000040082c| t | FUNC|0000000000000040| |.text
_Z4funci |00000000004007e4| T | FUNC|000000000000000f| |.text
.......
.......<SNIP>
符号func()
属于类型 - FUNC和Class - T, 即 - The symbol is in the text (code) section
.
头文件或任何前向声明究竟如何知道它指的是哪个定义?
我知道 .cpp 文件是独立编译的,我们需要一个头文件或前向声明来访问另一个 .cpp 文件的成员。但是当我们声明一个成员时,我们并没有明确地告诉编译器从哪里得到定义。
这是我能想到的一个案例: 假设我有两个 cpp 文件 'one.cpp' 和 'two.cpp'。 'one.cpp' 和 'two.cpp' 都有一个成员 'int func(int x)',它们具有不同的实现(但具有确切的名称和格式)。 如果我们在这两个文件之外的某处有这个函数的头文件或声明,编译器如何知道采用哪个定义?
解析每个声明的定义由链接器执行。每个声明必须有一个唯一的定义。虽然可以多次声明一个函数,但每个函数必须在所有要链接的编译单元中只定义一次。如果具有相同签名的函数有多个定义,则链接器将抛出错误并拒绝完成可执行文件的构建。
我建议您创建您描述的示例文件,并尝试将它们构建为单个可执行文件。您将看到我在此处描述的错误。
If we have a header file or declaration of this function somewhere outside these two files, how does the compiler know which definition to take?
它是linker,它将一个或多个目标文件或库作为输入,并将它们组合起来生成一个可执行文件。这样做时,它解析对外部符号的引用,即它从其他“.obj”文件和外部库中查找所有外部函数和全局变量的定义,将最终地址分配给 procedures/functions 和变量,并修改代码和反映新地址的数据。
让我们考虑一下您提到的例子:
Say I have two cpp files 'one.cpp' and 'two.cpp'. Both 'one.cpp' and 'two.cpp' have a member 'int func(int x)' that have different implementations....
说,one.cpp
:
int func(int x)
{
return x+1;
}
和two.cpp
:
int func(int x)
{
return x+2;
}
和声明 func()
函数的头文件,比如 myinc.h
:
int func(int x);
和正在调用 func()
的 main()
,比如 main.cpp
:
#include <iostream>
#include <myinc.h>
int main()
{
int res;
res = func(10);
std::cout << res << std::endl;
return 0;
}
我可以创建 main.cpp
的目标文件,因为目标文件可以引用未定义的符号。
>g++ -I . -c main.cpp
现在让我们使用nm
命令检查对象文件main.o
,输出是:
Symbols from main.o:
Name Value Class Type Size Line Section
_GLOBAL__I_main |0000000000000078| t | FUNC|0000000000000015| |.text
_Z41__static_initialization_and_destruction_0ii|0000000000000038| t | FUNC|0000000000000040| |.text
_Z4funci | | U | NOTYPE| | |*UND*
.......
.......<SNIP>
func()
函数Class为U,表示未定义。
编译器不会介意找不到特定函数的定义,它只会假设该函数是在另一个文件中定义的。
另一方面,链接器可能会查看多个文件并尝试查找对未提及函数的引用。
因此,当我们尝试从目标文件 one.o
、two.o
和 main.o
创建可执行文件时:
>g++ two.o one.o main.o -o outexe
one.o: In function `func(int)':
one.cpp:(.text+0x0): multiple definition of `func(int)'
two.o:two.cpp:(.text+0x0): first defined here
collect2: ld returned 1 exit status
在这里您可以看到链接器为 func()
抛出 multiple definition
错误,因为它找到了 func()
.
c++中有一个one definition rule,表示:
In the entire program, an object or non-inline function cannot have more than one definition; if an object or function is used, it must have exactly one definition. You can declare an object or function that is never used, in which case you don't have to provide a definition. In no event can there be more than one definition.
因此,该程序违反了 ODR,因为它包含同一函数声明的两个定义。
如果我们不向链接器提供 one.o
或 two.o
目标文件,意味着,如果我们只提供 func()
的一个定义,它将生成 exe:
>g++ one.o main.o -o outexe
如果我们检查 outexe
,我们得到:
Symbols from outexe:
Name Value Class Type Size Line Section
.....<SNIP>
_Z41__static_initialization_and_destruction_0ii|000000000040082c| t | FUNC|0000000000000040| |.text
_Z4funci |00000000004007e4| T | FUNC|000000000000000f| |.text
.......
.......<SNIP>
符号func()
属于类型 - FUNC和Class - T, 即 - The symbol is in the text (code) section
.