为什么使用内联函数的程序根据链接顺序和参数具有不同的行为

Why program using inline functions have different behaviors according to link order and parameters?

本文关键字:参数 顺序 函数 为什么 程序 链接      更新时间:2023-10-16

我知道这个问题的答案,但我们可以有一些有趣的分析。我们将学会玩得开心!

我已经使用 gcc 4.1.2 进行这些测试。

首先,此代码不是标准的,因为内联函数在不同的翻译单元中具有不同的定义。我知道那件事。但是,让我们分析一下正在发生的事情,并回答我将提出的三个问题。我们将从中学习:)

我会保持文件简单(例如没有 #ifndef 守卫(。

假设我有这些文件:

增量.h

inline int increment()
{
    static int value = 0;
    return ++value;
}

递减.h

int decrement();

递减.cpp

inline int increment()
{
    static int value = 0;
    return --value; // Attention to this
}
int decrement()
{
    return increment();
}

主.cpp

#include <iostream>
#include "increment.h"
#include "decrement.h"
using namespace std;
int main()
{
    cout << increment() << endl;
    cout << increment() << endl;
    cout << decrement() << endl;
}

如果我用这个制作文件编译它们:

CC=gcc
CFLAGS=-I. -O2
crazy: main.o decrement.o
        $(CC) -lstdc++ main.o decrement.o -o crazy
main.o: main.cpp increment.h decrement.h
        $(CC) $(CFLAGS) -c main.cpp -o main.o
decrement.o: decrement.cpp decrement.h
        $(CC) $(CFLAGS) -c decrement.cpp -o decrement.o
clean:
        rm -f *.o *.~ crazy

输出为:

1
2
1

如果我从生成文件中删除 -O2 标志:

  CFLAGS=-I.

输出为:

1
2
3

如果我还更改了main.o和decrement.o的顺序(像我刚才所做的那样不带-O2标志(:

$(CC) -lstdc++ decrement.o main.o -o crazy

结果是:

-1
-2
-3

这是怎么回事?为什么 -O2 标志和目标文件链接的顺序会以这种方式更改输出?

这里有两个翻译单元:main.cpp和decment.cpp

让我们替换 increment.h 和 decrement.h 的 #include 指令,看看预处理器传递后 main.cpp 和 decrement.cpp 是什么样子的(我不会替换其他包含(:

递减.cpp

inline int increment()
{
    static int value = 0;
    return --value; // Attention to this
}
int decrement()
{
    return increment();
}

主.cpp

#include <iostream>
inline int increment()
{
    static int value = 0;
    return ++value;
}
int decrement();
using namespace std;
int main()
{
    cout << increment() << endl;
    cout << increment() << endl;
    cout << decrement() << endl;
}

递减的情况下.cpp它会导出类似于链接器的符号:

(inline) int increment::value = 0;
inline int increment(); // exported only if -O2 flag is NOT specified, with -O2 it is inlined
int decrement();

主.cpp的情况下,它导出:

int (inline) increment::value = 0;
inline int increment(); // exported only if -O2 flag is NOT specified, with -O2 it is inlined
int main();

如果增量实际上是内联的(-O2 标志集(,链接器会在某处定义"int::increment value = 0",这些转换单元将变为:

递减.cpp

int decrement()
{
    return --increment::value;
}

主.cpp

#include <iostream>
int decrement();
using namespace std;
int main()
{
    cout << ++increment::value << endl;
    cout << ++increment::value << endl;
    cout << decrement() << endl;
}

所以我们得到这个行为:

1
2
1

但是如果我们删除 -O2 标志,编译器不会内联函数,而是为它们创建一个主体(因此调试起来会更容易,因为调试器将有一个独特的位置来放置该函数的断点(。因此,将为"inline int increment(("创建两个主体。一个递减.cpp (decrement.o(,这将递减值,另一个在 main.cpp (main.o( 中,这将递增值。

在链接时,如果"int increment(("不是内联的,则多个主体定义将导致链接器错误,因为函数的主体只应定义一次。但是由于它是内联的,链接器假设"inline int increment(("的所有主体在所有翻译单元中都是相同的,因为标准规定所有内联函数必须在任何地方都具有相同的主体,链接器只是选择其中的第一个。

如果您链接:

$(CC) -lstdc++ main.o decrement.o -o crazy

第一个主体是 main.o 中的主体,它增加值。所以你得到:

1
2
3

但是,如果您链接:

    $(CC) -lstdc++ decrement.o main.o -o crazy

链接器从 decrement.o 中选取您的主体,这实际上递减了值。所以你得到:

-1
-2
-3

问题:如果一个主体 (main.o( 将值初始化为 0,而另一个主体 (decrement.o( 将其初始化为例如值 = 3,会发生什么情况?你觉得怎么样?