c语言学习从内存看extern:两个文件如何共享同一块全局区内存
第一个文件:main.c
实例
#include <stdio.h>
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
第二个文件:support.c
实例
#include <stdio.h>
extern int count;
void write_extern(void)
{
printf("count is %d\n", count);
}
在这里,第二个文件中的 extern 关键字用于声明已经在第一个文件 main.c 中定义的 count。现在 ,编译这两个文件,如下所示:
$ gcc main.c support.c
这会产生 a.out 可执行程序,当程序被执行时,它会产生下列结果:
count is 5
从内存堆栈视角,解析这对 "跨文件协作" 的 C 代码
咱们先打个比方:这两个文件就像公司的两个部门 ——main.c是 "业务部",support.c是 "技术部"。全局变量count是两部门共用的 "业绩报表",extern关键字就是 "报表借阅证"—— 技术部凭这个证,就能查看业务部维护的报表数据。
先理清代码协作流程
main.c里定义了全局变量count,赋值 5 后调用support.c里的write_extern函数;support.c用extern声明要使用count,最后打印出 5。整个过程就像业务部填好报表,技术部凭借阅证查看并汇报数据。今天咱们重点看:这张 "业绩报表"(count)存在内存的哪里?两个部门(文件)是怎么通过 "借阅证"(extern)访问它的?
内存区域再梳理:全局区是 "共享数据库"
C 程序运行时,内存主要分三大块,这个例子的核心在第一块:
- 全局区(静态存储区):像公司的共享数据库,存放全局变量(无论在哪个文件定义),程序启动时创建,结束时销毁,所有文件都能访问(只要有 "权限")。
- 栈(Stack):像员工的临时工作台,函数调用时分配栈帧,结束后自动回收,存放局部变量和函数调用信息。
- 堆(Heap):像仓库,需要手动申请释放,这两个文件都没用到。
逐文件拆解:内存里的 "跨部门协作"
1.main.c的内存操作:定义全局变量 + 调用函数
c
运行
// main.c
int count; // 全局变量定义
extern void write_extern(); // 声明外部函数
int main() {
count = 5; // 给全局变量赋值
write_extern(); // 调用外部函数
}
- 全局变量count的内存分配:
程序编译链接后,count作为全局变量被分配到全局区。未初始化的全局变量会被自动设为 0(就像共享数据库里新建了一条记录,初始值为 0)。 - main函数的栈帧:
程序运行时,main函数在栈上获得一块栈帧(业务部的临时工作台)。因为main里没有普通局部变量,栈帧里主要存放函数调用的必要信息(比如返回地址)。 - count = 5的操作:
CPU 从全局区找到count的内存地址,把 5 写进去(业务部更新共享数据库的记录)。这个过程不涉及栈内存,直接操作全局区。 - 调用write_extern:
调用时,栈上临时创建write_extern的栈帧(技术部的临时工作台),函数执行完后自动释放。
2.support.c的内存操作:用extern访问全局变量
c
运行
// support.c
extern int count; // 声明要使用全局变量
void write_extern(void) {
printf("count is %d\n", count); // 访问并打印
}
- extern int count的作用:
这行不是定义变量,而是告诉编译器:"count在其他文件(比如main.c)里已经定义了,它在全局区,我要访问它 "(技术部出示借阅证,说明要访问共享数据库的记录)。不分配新内存,只是确认访问权限。 - write_extern函数的栈帧:
被调用时,栈上分配临时栈帧(技术部的工作台)。printf函数调用时,会从全局区读取count的值(5),作为参数传递给printf(从共享数据库查记录,然后汇报)。 - 打印完成后:
write_extern的栈帧被释放(技术部工作台清空),但全局区的count依然存在(共享数据库的记录没变)。
3. 编译链接与运行:内存如何 "合并"
- 编译阶段:两个文件分别被编译成目标文件(.o)。main.c的目标文件包含count的定义和main函数;support.c的目标文件包含write_extern函数和count的外部声明。
- 链接阶段: linker(链接器)会把两个目标文件合并,将support.c中extern int count与main.c中定义的count关联起来(让借阅证绑定到具体的数据库记录),确保两者指向全局区的同一个内存地址。
- 运行阶段:加载到内存后,全局区的count只有一份,两个函数通过同一个地址访问它(就像共享数据库的同一条记录,两个部门看到的是同一个值)。
内存快照:跨文件共享的本质
plaintext
┌─────────────────────────────────┐
│ 全局区 (静态存储区) │
│ ┌─────────────────┐ │
│ │ count: 0→5 │ │ ← 唯一的全局变量,被两个文件共享
│ │ (地址:0x123456) │ │ ← main.c定义,support.c通过extern访问
│ └─────────────────┘ │
├─────────────────────────────────┤
│ 栈 (Stack) │
│ ┌─────────────────────────┐ │
│ │ main函数栈帧 │ │ ← 业务部工作台,调用时存在
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ write_extern栈帧(临时)│ │ ← 技术部工作台,调用时存在,用完释放
│ │ (包含printf临时数据) │ │
│ └─────────────────────────┘ │
├─────────────────────────────────┤
│ 堆 (Heap) │
│ (未使用,为空) │
└─────────────────────────────────┘
extern的核心价值:全局区的 "共享契约"
- 不重复分配内存:如果support.c里不用extern,而是重新定义int count,链接时会报错 "重复定义"(就像技术部自己建了个同名报表,和业务部的冲突)。extern保证全局区只有一份count,节省内存且数据一致。
- 跨文件可见性:全局变量默认可以被所有文件访问,但需要用extern声明(就像共享数据库的记录默认存在,但其他部门要查,得先说明要查哪条)。
- 与栈无关:extern变量存放在全局区,和栈没有关系。栈只负责函数调用的临时操作,不参与全局变量的存储和共享。
关键结论:全局区是 "跨文件协作的舞台"
- 两个文件通过全局区共享count,extern只是 "声明访问权限" 的工具,不影响变量的存储位置。
- 栈内存像临时工作台,只在函数调用时存在,用完就清,不参与跨文件数据共享。
- 这种协作模式就像公司各部门共享数据库:一份数据,多部门访问,既保证一致性,又避免重复存储。
标题:
- 《C 语言跨文件协作:extern是钥匙,全局区是共享仓库》
- 《从内存看extern:两个文件如何共享同一块全局区内存》
简介:
通过分析main.c和support.c的协作,揭示extern关键字的作用是声明对全局区变量的访问权限,两个文件共享全局区中唯一的count变量,栈仅用于函数调用的临时操作,展现全局区在跨文件数据共享中的核心作用。
关键词:
#C 语言 extern #跨文件共享 #全局区 #内存协作 #全局变量