0

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

我在这些测试中使用了 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;
}

如果我用这个 Makefile 编译它们:

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

如果我从 Makefile 中删除 -O2 标志:

  CFLAGS=-I.

输出是:

1
2
3

如果我还更改了 main.o 和 decrement.o 的顺序(就像我刚才所做的那样,让它没有 -O2 标志):

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

结果是:

-1
-2
-3

这里发生了什么?为什么 -O2 标志和目标文件链接的顺序会以这种方式改变输出?

4

1 回答 1

0

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

让我们用#include 指令替换increment.h 和decrement.h 来看看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;
}

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

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

对于main.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;
}

主文件

#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()”创建两个主体。一个在 decrement.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) 将 value 初始化为 0,而另一个 (decrement.o) 将其初始化为,例如 value = 3,会发生什么?你怎么看?

于 2013-12-05T19:57:31.753 回答