为什么这个C程序编译没有错误?

时间:2016-04-29 01:57:31

标签: c compilation

我有两个C文件,\s{2,}main.c

weird.c

但是,当我运行以下内容时:

// main.c    
int weird(int *);

int
main(void)
{
    int x, *y;

    y = (int *)7;
    x = weird(y);
    printf("x = %d\n", x);
    return (0);
}

// weird.c

char *weird = "weird";

我没有收到任何错误。为什么是这样?难道不应该至少存在链接错误吗?请注意,我只是在谈论编译文件 - 而不是运行它们。运行会产生分段错误。

3 个答案:

答案 0 :(得分:1)

这两个文件都没有包含任何会导致编译问题的错误。 main.c正确地声明(但没有定义)一个名为weird的函数并调用它,而weird.c正确地定义了一个名为char *的{​​{1}}。编译之后,main.o包含一个未解析的weird引用,而weird.o包含一个定义。

现在,这是有趣的部分:.o文件必然[*]包含有关weird类型的任何内容。只是姓名和地址。当链接发生时,现在说“嘿,主要期望这是一个weird,而你提供的实际上是int(*)(int *)!”为时已晚。所有链接器都会看到名称char *由一个对象提供并由另一个对象引用,并且像拼图一样将各个部分组合在一起。在C中,完全由程序员完成的工作是确保使用给定外部符号的所有编译单元都使用兼容类型声明它(不一定相同;对于什么是“兼容类型”有复杂的规则)。如果不这样做,结果行为是未定义的,可能是错误的。

[*]:实际上我可以想到目标文件包含类型的几种情况 - 例如,某些类型的调试信息,或用于链接时优化的特殊.o文件。但据我所知,即使存在类型信息 ,链接器也不会用它来警告这样的事情。

答案 1 :(得分:1)

我从Linux的角度来看。其他操作系统的细节可能有所不同。

当然,您的最新编辑无法编译,因为:

char *weird = "weird";
printf(weird); // wrong, remove this line

包含任何函数的语句(printf 。因此,假设您已删除该行。 clang-3.7 -Wall -Wextra -c main.c提出了几个警告:

main.c:7:9: warning: cast to 'int *' from smaller integer type 'int' [-Wint-to-pointer-cast]
    w = (int *)x;
        ^

main.c:8:9: warning: cast to 'int *' from smaller integer type 'int' [-Wint-to-pointer-cast]
    y = (int *)z;
        ^
main.c:7:16: warning: variable 'x' is uninitialized when used here [-Wuninitialized]
    w = (int *)x;
               ^
main.c:6:10: note: initialize the variable 'x' to silence this warning
    int x, *y, z, *w;
         ^
          = 0
main.c:8:16: warning: variable 'z' is uninitialized when used here [-Wuninitialized]
    y = (int *)z;
               ^
main.c:6:17: note: initialize the variable 'z' to silence this warning
    int x, *y, z, *w;
                ^
                 = 0
4 warnings generated.

从技术上讲,我猜你的例子是undefined behavior。那么实现不应该警告你,bad things可以发生(或不发生!)。

如果您在编译时和链接时启用了链接时优化可能会发出警告(但我不确定) ,也许与

gcc -flto -Wall -Wextra -O  -c main.c
gcc -flto -Wall -Wextra -O  -c weird.c
gcc -flto -Wall -Wextra -O  main.o weird.o -o program

如果您愿意,可以将gcc替换为clang。我想要求优化(-O)是相关的。

实际上我没有收到clang-3.7 -flto的警告但我收到警告(在最后一个链接命令中)gcc 6

 % gcc -flto -O -Wall -Wextra  weird.o main.o -o program  
 main.c:1:5: error: variable ‘weird’ redeclared as function
 int weird(int *);
     ^
 weird.c:3:7: note: previously declared here
 char *weird = "weird";
       ^
 lto1: fatal error: errors during merging of translation units
 compilation terminated.

(我正在为GCC解释,我很清楚,包括它的一些内部结构;对于clang它应该是类似的)

使用-flto 编译器(例如lto1与GCC)也在运行以进行链接(因此可以优化,例如inlining之间的translation units次呼叫}})。它使用存储在目标文件中的编译器中间表示(这些表示包含键入信息)。没有它,最后一个命令(例如你的clang main.o weird.o -o program)只是用适当的选项调用链接器 ld(例如crt0& C standard library

当前linkers不保留或处理任何type信息(迂腐地做一些type erasure,主要由编译器本身完成)。他们只是在一些简单 symbol table和流程relocations中管理符号(即C标识符)。目标文件中缺少类型信息(更准确地说,链接器已知的符号表)是C ++需要name mangling的原因。

详细了解ELF,例如elf(5)object filesexecutables使用的格式。

clanggcc替换为clang -vgcc -v以了解正在发生的事情(因此它会显示基础cc1或{{1} }或lto1进程)。

正如其他人解释的那样,你真的应该共享一个共同的ld - d头文件(如果C代码不是机器生成的,而是手写的)。某些C代码生成器可能会避免生成头文件,并会在每个生成的C文件中生成相关(和相同)声明。

答案 2 :(得分:0)

如果要构建可执行程序,则必须链接对象。

但现在,您只需编译源代码。

编制者的想法 "啊,你以后会编译怪异的。好的。我只是编译这个"