同一个变量在两个 .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博客

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

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


相关推荐
csdn_aspnet5 小时前
C++ 3个有序点的方向(Orientation of 3 ordered points)
c++·算法
li星野6 小时前
相位补偿学习记录:把被颅骨“打乱“的声波重新对齐
学习
小七在进步6 小时前
string的使用(2)
c++
Z21-X7 小时前
云原生运维学习笔记:ECShop全链路容器化部署与Docker底层资源隔离深度剖析
运维·笔记·学习·云原生
青少儿编程课堂7 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:折半搜索优化子集枚举(Meet in the Middle)解析
c++·python·算法·bfs·信息学竞赛
red_redemption7 小时前
自由学习记录(230)
学习
wo_jack9 小时前
C++ 入门笔记(十七):list 入门 —— 为什么 list 不能 sort?
数据结构·c++·面试·stl·list
青少儿编程课堂10 小时前
蓝桥杯青少组图形化 + 全国青少年信息素养大赛 + 电子学会Scratch考级:校园连连看解析
c++·python·算法·bfs·信息学竞赛
草莓熊Lotso10 小时前
【C++ 基于微服务的即时通讯系统】文件存储子服务实战:UUID生成、文件IO封装与全接口单元测试
开发语言·网络·数据库·c++·微服务·架构·单元测试
晓梦林11 小时前
BUUCTF-菜刀流量-MISC-学习笔记
笔记·学习