可以编译以下代码并为函数输出未定义的警告,而不包括任何头文件.

// gcc a.c -std=c17
int main(){
    printf("hello\n");
}

这使得我无法定义与库函数同名的函数,如以下代码所示:

int printf(const char* str, ...) {
    return 0;
}

int main() {
    printf("hello 1\n");
    printf("hello %d\n", 2);
    printf("hello 3\n");
}

输出:

hello 1
hello 3

第一次调用了printf,第三次调用了库函数,但第二次调用了我定义的函数.这里没有报告重新定义的错误,也不总是调用库函数或我定义的函数.这是编译器中的错误吗?

GCC版本:GCC(Debian 12.2.0-14)12.2.0

推荐答案

首先,需要注意的是,C标准认为定义与库函数同名的函数是undefined behavior.也就是说,像GCC这样的实现通过将库函数定义为weak symbols来实现这一点,这意味着实现可以定义它们自己的此类函数版本以覆盖库版本.

printf函数是defined,作为默认链接的标准C库的一部分.在stdio.h文件中是declared.

当您编译第一段代码时,您可能会收到一条警告,说明printf未声明或正在使用默认声明.函数f的默认隐式声明是int f(),即接受未指定数量的参数并返回int的函数.这(几乎)与实际的声明兼容,因此在实践中它最终可以按预期工作.

第二段代码的问题与缺少标头无关.事实上,由于您的定义与头中的声明兼容,因此这样做不会有错误,并且行为将是相同的.

行为上的差异是由于GCC所做的优化.当它看到对printf的调用只有一个格式字符串以换行符结尾时,它会将其优化为对puts的调用,因为可观察到的行为将是相同的.如果您使用-S进行编译,并查看生成的程序集,您将看到实际发生的情况:

main:
        push    rbp
        mov     rbp, rsp
        mov     edi, OFFSET FLAT:.LC0
        call    puts                      // printf call optimized away
        mov     esi, 2
        mov     edi, OFFSET FLAT:.LC1
        mov     eax, 0
        call    printf                    // printf call not optimized
        mov     edi, OFFSET FLAT:.LC2
        call    puts                      // printf call optimized away
        mov     eax, 0
        pop     rbp
        ret

当然,如果给出了用户定义的printf实现,那么这种优化可能就不适用了.如果使用标志-fno-builtin-printf进行编译,则不会链接标准的printf函数,也不会进行这种优化.

C++相关问答推荐

C中空终止符后面的数字?

MISRA C:2012 11.3违规强制转换(FLOAT*)到(uint32_t*)

有没有更简单的方法从用户那里获取数据类型来计算结果

难以理解Makefile隐含规则

如果包含路径不存在,我的编译器可以被配置为出错吗?(GCC、MSVC)

如何跨平台处理UTF-16字符串?

如何在ASM中访问C struct 成员

在为hashmap创建加载器时,我的存储桶指向它自己

tick.q中的Kdb+键控表语法

如何在C中使数组变量的值为常量?

MacOS下C++的无阻塞键盘阅读

用C++构建和使用DLL的困惑

C语言中奇怪的输出打印数组

在运行时判断C/C++指针是否指向只读内存(在Linux操作系统中)

如何使用WRITE()以指针地址的十六进制形式写入标准输出

将size_t分配给off_t会产生符号转换错误

C语言中的指针和多维数组

中位数和众数不正确

如何为avr atmega32微控制器构建C代码,通过光电二极管捕获光强度并通过串行通信传输数据

在 C 中传递参数时出现整数溢出