C语言程序内存布局

面试的重点:
堆区 :用于动态内存分配,由程序员手动管理(malloc/free),向高地址方向增长。
栈区 :用于存储局部变量、函数参数和返回地址,自动管理,向低地址方向增长。
变量的分类

全局变量
所有函数外部定义的变量
cpp
#include <stdio.h>
int globalVar = 100; // 全局变量
void function() {
printf("在function中,globalVar = %d\n", globalVar);
globalVar = 200; // 修改全局变量
}
int main() {
printf("程序开始,globalVar = %d\n", globalVar);
function();
printf("调用function后,globalVar = %d\n", globalVar); // 输出200
return 0;
}
局部变量
函数内部或代码块内部定义的变量
默认为自动存储类型(auto),函数调用时创建,函数返回时销毁
如果局部变量与全局变量同名,在函数内部优先使用局部变量
cpp
#include <stdio.h>
int x = 30; //全局变量
void function() {
int x = 10; // 局部变量,必须要先初始化
printf("函数内部 x = %d\n", x);
{
int y = 20; // 代码块内的局部变量
printf("代码块内部 y = %d\n", y);
}
// printf("y = %d\n", y); // 错误:y在这里不可见
}
int main() {
int x = 5; // main函数的局部变量
printf("main函数内 x = %d\n", x);
function();
printf("调用function后,main函数内 x = %d\n", x); // x仍然是5
return 0;
}
存储的关键字
auto
cppvoid function() { auto int x = 10; // 等同于 int x = 10; // ... }stastic
1.用于变量
静态局部变量
cpp#include <stdio.h> void counter() { static int count = 0; // 静态局部变量 count++; printf("函数被调用了 %d 次\n", count); } int main() { counter(); // 输出:函数被调用了 1 次 counter(); // 输出:函数被调用了 2 次 counter(); // 输出:函数被调用了 3 次 return 0; }静态全局变量
cpp//仅限于定义它的源文件内,内部链接,其他源文件不能访问 // file1.c static int staticGlobalVar = 100; // 静态全局变量 void function1() { printf("staticGlobalVar = %d\n", staticGlobalVar); } // file2.c extern int staticGlobalVar; // 错误:无法访问file1.c中的静态全局变量 void function2() { // 无法访问staticGlobalVar }用于函数
当static用于函数时,它将函数的链接属性从外部链接(external linkage)改为内部链接(internal linkage)。
其他源文件无法通过函数声明来访问该函数
cpp// file1.c static void privateFunction() { // 静态函数 printf("这是一个私有函数\n"); } void publicFunction() { // 普通函数 privateFunction(); // 可以在同一文件中调用 } // file2.c extern void privateFunction(); // 错误:无法访问file1.c中的静态函数 void anotherFunction() { // privateFunction(); // 错误:无法调用 }static函数的优点
隐藏实现细节:将辅助函数声明为static可以隐藏实现细节,只暴露必要的接口
避免命名冲突:防止不同源文件中的同名函数冲突
提高代码安全性:限制函数的访问范围,减少意外调用
extern
用于声明一个在其他源文件中定义的变量或函数。
用于变量
extern关键字告诉编译器,该变量在其他地方已经定义,只是在当前文件中声明。
cpp// file1.c int globalVar = 100; // 定义全局变量 // file2.c extern int globalVar; // 声明外部变量 void function() { printf("globalVar = %d\n", globalVar); globalVar = 200; // 修改全局变量 }用于函数
在C语言中,函数默认具有外部链接属性,可以被其他源文件访问。使用extern关键字可以显式声明一个外部函数。
cpp// file1.c void utilityFunction() { printf("这是一个实用函数\n"); } // file2.c extern void utilityFunction(); // 声明外部函数(可以省略extern) void anotherFunction() { utilityFunction(); // 调用在file1.c中定义的函数 }extern关键字用于函数的作用
显式声明外部函数:明确指出函数在其他源文件中定义
提高代码可读性:使代码更加清晰,表明函数来自外部
解决前向引用问题:在函数定义之前使用函数
register(单片机会用)
register关键字建议编译器将变量存储在CPU寄存器中,以提高访问速度。
cppvoid function() { register int counter; // 建议将counter存储在寄存器中 for(counter = 0; counter < 1000; counter++) { // 频繁访问counter } }//单片机里会遇到现代编译器通常会自动进行寄存器优化,register关键字的作用不如以前明显。
const(重点)
const关键字用于声明一个常量,即一旦初始化后就不能再修改的变量。
cppconst int MAX_SIZE = 100; // MAX_SIZE = 200; // 错误:不能修改const变量与指针使用
常量指针
指向常量的指针 :指针指向的内容不能通过该指针修改
cppconst int *p;指针常量
指针本身的值(即指向的地址)不能修改(指针本身是常量)
cppint * const p;指向常量的常量指针
cpp//内存和地址都不能修改 const int * const p;
用于参数
const关键字可以用于函数参数,表示函数不会修改传入的参数:
cppvoid printArray(const int arr[], int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); // arr[i] = 0; // 错误:不能修改const数组元素 } printf("\n"); }用于函数返回值
cppconst char* getVersion() { return "v1.0.0"; // 返回一个不应被修改的字符串 }
总结:变量的存储区域比较
|----------|-----------|----------|----------------------|
| 存储区域 | 存储内容 | 生命周期 | 特点 |
| 栈区 | 局部变量、函数参数 | 函数调用期间 | 自动分配和释放,速度快 |
| 堆区 | 动态分配的内存 | 手动控制 | 需要手动管理,使用malloc/free |
| 数据段 | 全局变量、静态变量 | 整个程序运行期间 | 自动初始化为0 |
| 代码段 | 程序的可执行代码 | 整个程序运行期间 | 只读 |

内联函数
C99标准引入了inline关键字,用于定义内联函数。内联函数是一种特殊的函数,编译器会尝试将其调用处直接替换为函数体,而不是生成函数调用指令。
好处:
-
减少函数调用开销:避免了函数调用的压栈、跳转和返回等操作
-
适用于简短、频繁调用的函数:对于复杂函数,内联可能不会带来性能提升
-
只是对编译器的建议:编译器可能会忽略inline关键字,不进行内联
-
可能增加代码体积:因为函数代码被复制到每个调用处
cpp
#include <stdio.h>
// 定义内联函数
static inline int max(int a, int b) { //根据C99标准,需要使用static inline组合
return a > b ? a : b;
}
int main() {
int x = 10, y = 20;
// 调用内联函数
int result = max(x, y);
printf("最大值是: %d\n", result);
return 0;
}
内联函数与宏定义
内联函数和宏定义都可以避免函数调用开销,但内联函数有以下优点:
-
类型安全:内联函数会进行类型检查,宏定义不会
-
求值一次:内联函数的参数只会被求值一次,避免了宏定义可能导致的多次求值问题
-
可以使用局部变量:内联函数可以定义局部变量,宏定义不能
-
可以使用条件语句和循环:内联函数可以包含复杂的控制结构
内存分配与释放函数
malloc - 内存分配
cpp
void *malloc(size_t size);
-
功能:分配指定字节数的内存空间
-
参数:
size:要分配的字节数
-
返回值:指向分配的内存的指针,如果分配失败则返回NULL
-
注意:分配的内存内容是未初始化的
realloc - 重新分配内存
cpp
void *realloc(void *ptr, size_t size);
-
功能:调整之前分配的内存块的大小
-
参数:
-
ptr:之前分配的内存块的指针(如果为NULL,则等同于malloc) -
size:新的内存块大小(如果为0且ptr不为NULL,则等同于free)
-
-
返回值:指向新分配内存的指针,如果分配失败则返回NULL
-
注意:
-
如果新的大小大于原来的大小,额外的内存是未初始化的
-
如果返回的指针与ptr不同,原来的内存块会被自动释放
-
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// 初始分配5个整数的空间
int *numbers = (int *)malloc(5 * sizeof(int));
if (numbers == NULL) {
printf("内存分配失败\n");
return 1;
}
// 初始化数组
for (int i = 0; i < 5; i++) {
numbers[i] = i * 10;
}
// 重新分配为10个整数的空间
int *new_numbers = (int *)realloc(numbers, 10 * sizeof(int));
if (new_numbers == NULL) {
printf("内存重新分配失败\n");
free(numbers);
return 1;
}
numbers = new_numbers;
// 初始化新增的元素
for (int i = 5; i < 10; i++) {
numbers[i] = i * 10;
}
// 使用扩展后的数组
printf("重新分配后的数组: ");
for (int i = 0; i < 10; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
// 释放内存
free(numbers);
return 0;
}
free
cpp
void free(void *ptr);
-
功能:释放之前由malloc、calloc或realloc分配的内存
-
参数:
ptr:要释放的内存块的指针(如果为NULL,则不执行任何操作)
-
返回值:无
-
注意:
-
释放后的内存不能再被访问
-
同一块内存不能被释放两次(双重释放)
-
只能释放由malloc、calloc或realloc分配的内存
-
内存操作常见问题
cpp
void memoryLeak() {
int *p = (int *)malloc(sizeof(int));
*p = 10;
// 函数结束时没有调用free(p),导致内存泄漏
}
//解决方法:确保每次malloc、calloc或realloc都有对应的free。
int *p = (int *)malloc(sizeof(int));
*p = 10;
free(p);
*p = 20; // 错误:使用已释放的内存
//解决方法:释放内存后将指针设置为NULL。
int *p = (int *)malloc(sizeof(int));
*p = 10;
free(p);
p = NULL; // 防止使用已释放的内存
char *str = (char *)malloc(5);
strcpy(str, "Hello, World!"); // 错误:写入超过分配大小的数据
//解决方法:分配足够大的内存空间
char *str = (char *)malloc(15);
strcpy(str, "Hello, World!"); // 正确:分配了足够的空间




