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只是 "声明访问权限" 的工具,不影响变量的存储位置。
  • 栈内存像临时工作台,只在函数调用时存在,用完就清,不参与跨文件数据共享。
  • 这种协作模式就像公司各部门共享数据库:一份数据,多部门访问,既保证一致性,又避免重复存储。

标题:

  1. 《C 语言跨文件协作:extern是钥匙,全局区是共享仓库》
  2. 《从内存看extern:两个文件如何共享同一块全局区内存》

简介:

通过分析main.c和support.c的协作,揭示extern关键字的作用是声明对全局区变量的访问权限,两个文件共享全局区中唯一的count变量,栈仅用于函数调用的临时操作,展现全局区在跨文件数据共享中的核心作用。

关键词:

#C 语言 extern #跨文件共享 #全局区 #内存协作 #全局变量

相关文章

cython如何调用C语言的函数?_c 中如何调用python

在 Cython 中调用 C 语言函数主要通过以下几种方式实现:1. 使用 cdef extern 声明外部 C 函数基本语法cdef extern from "头文件.h":返回类型...

C/C++函数调用的奥秘_c++函数调用原理

在C/C++编程的世界里,函数调用是程序运行的核心机制之一。然而,许多程序员在日常开发中,往往只关注代码的逻辑,而忽略了函数调用背后的底层细节。今天,就让我们一起深入探索C/C++函数调用的全过程,从...

C++成员函数如何工作?this指针、name mangling 成员函数指针解析

0.引言 在C++面向对象编程中,成员函数是对象行为的核心载体。我们每天都在使用成员函数,但却很少深入思考其底层的实现机制:为什么成员函数可以直接访问成员变量?编译器如何区分不同类的同名函数?静态成员...

C语言入门:学生成绩管理程序的完善(1):用文件保存数据

这是C语言入门的第27篇文章。今天讲学生成绩管理程序的完善:怎样利用文件来保存数据。还是昨天的问题:我怎么知道一个文件的内容是什么?我怎么知道比如一行有多少个数,多少个数以后是换行?这是因为文件是我们...

C语言应用笔记:常用的printf打印输出不同类型数据

我叫程序员阿虾, 在终端前摸过太多凌晨, 熟悉printf这一行字带来的安心与危险。今天想跟你聊聊我踩过的坑, 和一些别人不常说的细节, 用第一人称把经验交给你, 有点唠叨, 希望你少走弯路。为什么要...

C语言应用笔记:简单的最大最小值比较

使用宏定义实现泛型比较函数,用于求取两个值的最大值和最小值。核心宏定义解析#define MAX(x, y) ((x) > (y) ? (x) : (y)) // 返回两个值中较大的一个 #de...