同一个变量在两个 .c 文件里类型不一致,程序会怎样?

Andrew Koenig《C 陷阱与缺陷(第二版)》4.5节

目录

为什么没人拦你

四种可能

结论

还有另一种"外部类型"


先看两个文件。

cpp 复制代码
/* a.c ------ 声明 n 是 int */
#include <stdio.h>
extern int n;

int main(void)
{
    printf("n = %d\n", n);
    return 0;
}
cpp 复制代码
/* b.c ------ 定义 n 是 long */
long n = 305419896;

一个变量叫 n,a.c 说它是 int,b.c 说它是 long。

这是一个无效的 C 程序。 但编译器不拦你,链接器不管,程序照样生成、照样能跑。

为什么没人拦你

书里说得很清楚:

这是一个无效的 C 程序,因为同一个外部变量名在两个不同的文件中被声明为不同的类型。然而,大多数 C 语言实现却不能检测出这种错误。 编译器对这两个不同的文件分别进行处理,这两个文件的编译时间甚至可以相差好几个月。因此,编译器在编译一个文件时,并不知道另一个文件的内容。连接器可能对 C 语言一无所知,因此它也不知道如何比较两个 n 的定义中的类型。

拆成两句:

  • 编译器一次只看一个文件。 编译 a.c 的时候,b.c 还没被打开过------它不会知道 n 在那边是什么类型?
  • 链接器只看符号名。 它在两个目标文件里各找到一个叫 n 的符号,把它俩接上,收工。类型?那是编译器的事,链接器不认得。

四种可能

书说运行时"存在很多的可能情况",列了四条:

1.编译器足够"聪明",检测到冲突,给你一条诊断消息。

2.int 和 long 的内部表示正好一样(书说尤其在 32 位机上)------程序碰巧正常工作,"本来错误的程序因为某种巧合却能够工作"。

类型 32 位系统 64 位系统
int 4 字节 4 字节
long 4 字节 8 字节

3.两者要求的存储大小不同,但共享存储空间的方式恰好对得上------也能工作。

也就指的是在int不溢出的情况下,int和long是生效的

4.共享方式使得给其中一个赋值,等于给另一个赋了个完全不同的值------程序不能正常工作。

这就是溢出的情况了,也是最坏的情况。


结论

因此,保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型,一般来说是程序员的责任。而且,"相同的类型"应该是严格意义上的相同。

注意后半句:"严格意义上的相同"。书马上给了一个容易踩的例子:

cpp 复制代码
/* 文件 1 */
char filename[] = "/etc/passwd";

/* 文件 2 */
extern char* filename;

书说:

尽管在某些上下文环境中,数组与指针非常类似,但它们毕竟不同。......这两个对 filename 的声明使用存储空间的方式是不同的;它们无法以一种合乎情理的方式共存。

两张图一摆就明白了:

图 4.1 里 filename 就是那 12 个格子本身;

图 4.2 里 filename 是一个盒子,盒子里装着一个箭头。

名字一样,布局完全不是一回事。

详见 :char p\[\]= "hello";q = p; 复制的是字符串吗?C 的"举隅法"陷阱-CSDN博客

改法书上给了两条,任选一条,两边一致就行:

cpp 复制代码
char filename[] = "/etc/passwd";   /* 文件 1 */
extern char filename[];            /* 文件 2 */

或者:

cpp 复制代码
char* filename = "/etc/passwd";    /* 文件 1 */
extern char* filename;             /* 文件 2 */

还有另一种"外部类型"

书在结尾提了第二种:

有关外部类型方面,另一种容易带来麻烦的方式是忽略了声明函数的返回类型,或者声明了错误的返回类型。

这就是 4.4 节那个 sqrt 的例子。书补了一句规则:

C 语言中的规则是,如果一个未声明的标识符后跟一个开括号,那么它将被视为一个返回整型的函数。因此,这个程序完全等同于 extern int sqrt();。

详见:调用函数时老是有莫名其妙地错误?函数的形参实参与返回值-CSDN博客

到这里书讲完了。总结就是:

声明和定义必须严格同类型,这件事标准没派给编译器,派给了你。


相关推荐
GEO从入门到精通9 小时前
课程怎么选?2026年GEO学习资源全梳理
学习
weixin_424183619 小时前
开放—封闭原则实战(下):基于策略的流程编排与编译期多态
c++·分布式·系统架构
wuminyu10 小时前
C++协程实现接收端的零拷贝Buffer管理原理剖析
java·linux·c语言·jvm·c++
优化Henry10 小时前
LTE 载波频率与频点配置详解
运维·网络·学习·5g·信息与通信
m0_3807438710 小时前
C++中的freopen的用法实例详解
开发语言·c++
xiaomu0012310 小时前
高湿环境床垫湿度管理技术框架:湿度阈值、排湿结构与材料选型
经验分享·学习·学习方法
zhangrelay11 小时前
ROS项目设计案例智能大模型正经乱答案例
linux·笔记·学习·ubuntu·机器人
probex_11 小时前
学习笔记:知识图谱是什么,在测试里能干什么
笔记·学习·知识图谱
jimy111 小时前
polymorph.cpp里面的“析构函数、智能指针”问题
开发语言·c++