两个文件中同一个函数/全局变量的不同声明

6

在C和C++中,当两个文件中对同一函数和全局变量进行不同的声明时,我有两个问题。

  1. Different function declarations

    Consider the following code fragments:

    file_1.c

    void foo(int a);
    
    int main(void)
    {
        foo('A');
    }
    

    file_2.c

    #include <stdio.h>
    
    void foo(char a)
    {
        printf("%c", a); //prints 'A' (gcc)
    }
    

    As we can see, the prototype differs from the definition located in file_2.c, however, the function prints expected value.

    If it comes to C++, the above program is invalid due to undefined reference to foo(int) at link time. It's probably caused by presence of other function signatures - in comparison with C, where a function name doesn't contain any extra characters indicating the type of function arguments.

    But when it comes to C then what? Since the prototypes with the same name have the same signature regardless of the number of arguments and its types, linker won't issue an error. But which type conversions are performed in here? Does it look like this: 'A' -> int -> back to char? Or maybe this behavior is undefined/implementation-defined ?

  2. Different declarations of a global variable

    We've got two files and two different declarations of the same global variable:

    file_1.c

    #include <stdio.h>
    
    extern int a;
    
    int main(void)
    {
        printf("%d", a); //prints 65 (g++ and gcc)
    }
    

    file_2.c

    char a = 'A';
    

    Both in C and C++ the output is 65.

    Though I'd like to know what both standards say about that kind of situation.

    In the C11 standard I've found the following fragment:

    J.5.11 Multiple external definitions (Annex J.5 Common extensions)
    There may be more than one external definition for the identifier of an object, with or without the explicit use of the keyword extern; if the definitions disagree, or more than one is initialized, the behavior is undefined (6.9.2).

    Notice that it refers to presence of two and more definitions, in my code there is only one, so I'm not sure whether this article is a good point of reference in this case...


1
你说,“这个函数打印了预期值”。但是你预期的数字是多少?为什么? - David Schwartz
1
它看起来像这样:“A”-> int->返回char吗?请注意,在C中,'A'是int。由于声明/定义不一致,这两个示例都是未定义的行为。 - Daniel Fischer
@DavidSchwartz 我的意思是它没有打印任何随机值。 - Quentin
@DanielFischer 对,我忘了这个。谢谢你的回答。 - Quentin
4个回答

5
Q1. 根据C99规范第6.5.2.2.9节,以下情况在C语言中属于未定义行为:
如果函数的类型与表示被调用函数的表达式所指向的类型(即表达式“指向”的函数)不兼容,则行为未定义。
表达式“指向”一个以int为参数的函数,而该函数被定义为以char为参数。
Q2. 对于变量的情况也属于未定义行为,因为你正在读取或分配一个int到/从char。假设是4字节的整数,这将访问比其有效内存位置多3个字节。您可以通过声明更多的变量来测试此问题,如下所示:
char a = 'A';
char b = 'B';
char c = 'C';
char d = 'D';

@undur_gongor 你说得对,我应该说“访问”而不是“写入”,因为他并不是从另一个文件中写入。我在上一句话中已经说过“读取或写入”,但在第二句话中只提到了写入。 - Sergey Kalinichenko
现在更清楚了。for循环声明应该是4个不同的变量,对吧? - undur_gongor
@undur_gongor 你说得对,在之前的编辑中(现在不可见,因为是在前五分钟内完成的),我在那里有一个 int 变量,但后来我意识到编译器可能会用两个或三个字节“填充” a,所以决定改用 char - Sergey Kalinichenko

2

这就是为什么你要把声明放到头文件中,这样即使C编译器也能捕捉到问题。

1)

结果几乎是随机的;在你的情况下,“char”参数可能会被传递为int(例如在寄存器中或甚至在堆栈上保持对齐等)。或者由于字节顺序相同而幸运地保留了最低位字节。

2)

由于字节顺序和一些添加的“0”字节填充段,很可能是一个幸运的结果。同样,不要依赖它。


在C语言中,charshort类型的参数总是会被提升为int类型。 - Pete Becker
@PeteBecker:能否请您提供更多细节?我不理解这个信息。 - undur_gongor
@undur_gongor - 这个答案断言说 " 'char' 参数 可能 会被传递为 int...",但在 C 中,char 参数总是作为 int 传递。 - Pete Becker
@PeteBecker,如果我没记错的话,对于没有原型和传递给...的参数的函数,会发生提升,但这是强制性的唯一情况。否则,您需要兼容的类型,而char与int不兼容(甚至与unsigned charsigned char也不兼容)。 - AProgrammer
@AProgrammer - 你是对的。我刚查了一下,准备纠正我之前说的话。谢谢。 - Pete Becker
显示剩余2条评论

1

C++中的重载函数之所以能够工作,是因为编译器将每个独特的方法和参数列表组合编码成一个唯一的名称,用于链接器。这个编码过程被称为名称修饰,而反向过程则被称为名称解析。

但在C语言中并没有这样的机制。当编译器遇到一个未定义的符号(变量或函数名),它会假设该符号在其他模块中定义,并生成一个链接器符号表条目,留待链接器处理。在这里我们没有参数检查。

此外,在这里也没有类型转换。在主函数中,你将一个值发送给foo。这是汇编代码:

movl    $65, (%esp)
call    foo

而 foo 通过从堆栈中取走它来读取它。由于它的输入值被定义为 char,因此它将输入值存储在 al 寄存器中(一个字节):

movb    %al, -4(%ebp)

对于大于256的给定输入,您将在foo中看到变量a循环超过256。

关于您的第二个问题,在C中,初始化变量和函数的符号被定义为强符号,并且不允许多个强符号,但我不确定这是否适用于C ++。


1

只是让你知道,我无意中找到了涵盖这两个问题的C11标准段落 - 它是6.2.7.2:

所有引用同一对象或函数的声明都应具有兼容类型;否则,行为未定义。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接