【问题标题】:How to trace out why gcc and g++ produces different code如何找出 gcc 和 g++ 产生不同代码的原因
【发布时间】:2013-11-27 14:45:27
【问题描述】:

是否可以看到 gcc 和 g++ 编译过程背后发生了什么?
我有以下程序:

#include <stdio.h>
#include <unistd.h>

size_t sym1 = 100;
size_t *addr = &sym1;

size_t *arr = (size_t*)((size_t)&arr + (size_t)&addr);

int main (int argc, char **argv)
{
    (void) argc;
    (void) argv;

    printf("libtest: addr of main(): %p\n", &main);
    printf("libtest: addr of arr: %p\n", &arr);

    while(1);
    return 0;
}

为什么使用 g++ 生成二进制文件却没有错误,而使用 gcc 有错误?
我正在寻找一种方法来追踪导致它们行为不同的原因。

# gcc test.c -o test_app
test.c:7:1: error: initializer element is not constant
# g++ test.c -o test_app

我认为原因实际上可能是 gcc 使用 cc1 作为编译器,而 g++ 使用 cc1plus。
有没有办法更精确地输出实际所做的事情?
我尝试使用 -v 标志,但输出非常相似。是否有不同的标志传递给链接器?
比较两个编译过程并找出它们之间的差异的最简单方法是什么?

【问题讨论】:

  • 因为C!=C++.
  • 其中一个原因是它们链接到不同的库。
  • 因为 gcc 编译 c 代码而 g++ 编译 c++ 代码,它们是相似的,但仍然是不同的语言。我不是专家,但我的猜测只是你在那里尝试的东西(将非常量分配给全局)在 c 中根本不可能。
  • 请查看主题中的实际问题(不仅是标题):-) 我想知道一些方法来输出编译过程中发生的事情。使用了哪些子程序,传递了哪些标志,链接了哪些库......我想看看一些背景......
  • @MikhailKalashnikov 将 -v 标志添加到 gcc 或 g++

标签: c++ c gcc compiler-construction


【解决方案1】:

在这种情况下,gcc 不会产生任何结果,因为您的程序不是有效的 C。正如编译器所解释的,初始化元素(用于初始化全局变量 arr 的表达式)不是常量。

C 要求初始化表达式是编译时常量,这样局部变量的内容就可以放在可执行文件的数据段中。这对于arr 是无法做到的,因为所涉及的变量的地址直到链接时才知道,并且动态链接器无法简单地填充它们的总和,就像addr1 的情况一样。 C++ 允许这样做,因此g++ 生成初始化代码,用于计算非常量表达式并将它们存储在全局变量中。此代码在调用main() 之前执行。

可执行文件cc1cc1plus 是编译器实现的内部细节,因此与观察到的行为无关。相关事实是 gcc 期望有效的 C 代码作为其输入,而 g++ 期望有效的 C++ 代码。您提供的代码是有效的 C++,但不是有效的 C,这就是 g++ 编译它而 gcc 不编译它的原因。

【讨论】:

  • 自 2011 年以来,C++ 也无效,因为 while(1);
  • 啊,但最好说一下为什么初始化表达式不是一个常量表达式,这是因为所涉及的地址直到链接时才知道。在 C++ 中,我相信编译器会在这种情况下输出相当于一个迷你构造函数的内容。 (不完全是构造函数,但通常使用构造函数机制运行。)
  • 但是为什么“addr”变量没有错误? “sym1”的地址直到链接时间才知道,不是吗?为什么 gcc 可以编译“int addr = &sym1”,但不能编译“int addr = &sym1 + &sym2”? (为什么 g++ 可以同时编译?)
  • @MikhailKalashnikov 最后一个问题的答案是编译器必须将指令放入目标文件中,以便链接器遵循以在链接时将正确的地址放在正确的位置这些指令(称为relocations) 的格式非常简单,可能无法添加。 (顺便说一下,我使用了一般意义上的“指令”,与“CPU 架构的指令”无关)
  • @user4815162342 :顺便说一句,我在 C99 中发现了另一个相关位,这使得 6.6 第 6 段中来自 OP 的表达式非法:“整数常量表达式应具有整数类型,并且只能具有以下操作数是整数常量、枚举常量、字符常量、sizeof 表达式,其结果是整数常量,以及作为强制转换的直接操作数的浮点常量。整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型, 除了作为sizeof 运算符的操作数的一部分。”
【解决方案2】:

这里潜伏着一个更有趣的问题。考虑以下测试用例:

#include <stdint.h>

#if TEST==1
void *p=(void *)(unsigned short)&p;
#elif TEST==2
void *p=(void *)(uintptr_t)&p;
#elif TEST==3
void *p=(void *)(1*(uintptr_t)&p);
#elif TEST==4
void *p=(void *)(2*(uintptr_t)&p);
#endif

gcc(即使带有非常保守的标志 -ansi -pedantic-errors)拒绝测试 1 但接受测试 2,接受测试 3 但拒绝测试 4。

由此我得出结论,在检查初始化程序是否为常量表达式之前,一些易于优化的操作(如强制转换为相同大小的对象或乘以 1)会被消除。

所以 gcc 可能会接受一些根据 C 标准它应该拒绝的东西。但是当你让它们稍微复杂一点时(比如将一个强制转换的结果添加到另一个强制转换的结果 - 无论如何添加两个地址可能会产生什么有用的价值?)它会注意到问题并拒绝表达式。

【讨论】:

    猜你喜欢
    • 2014-11-04
    • 2014-04-12
    • 1970-01-01
    • 2019-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-18
    相关资源
    最近更新 更多