全部版块 我的主页
论坛 数据科学与人工智能 IT基础 C与C++编程
579 0
2025-12-02
存储区域 存储内容 生命周期 特点
栈区 局部变量、函数参数 函数调用期间 自动分配和释放,速度快
堆区 动态分配的内存 手动控制 需要手动管理,使用malloc/free
数据段 全局变量、静态变量 整个程序运行期间 自动初始化为0
代码段 程序的可执行代码 整个程序运行期间 只读

变量分类与存储类型

全局变量:在所有函数外部定义的变量,其作用域从定义位置开始到文件结尾,存储于数据段中。

#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存储类别。它们在函数调用时创建,在函数返回后自动销毁。

  • 若局部变量与全局变量同名,则在函数内部优先访问局部变量。
#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:用于声明自动变量,即局部变量。由于这是默认行为,因此通常无需显式写出。

void function() {
    auto int x = 10;  // 等同于 int x = 10;
    // ...
}

static

  1. 静态局部变量:仅在定义它的函数内可见,但生命周期延长至整个程序运行期,值在多次调用间保持不变。
  2. 静态全局变量:限制变量的作用域为当前源文件,避免与其他文件中的同名变量冲突。
#include <stdio.h>

void counter() {
    static int count = 0;  // 静态局部变量
    count++;
    printf("函数被调用了 %d 次\n", count);
}

int main() {
    counter();  // 输出:函数被调用了 1 次
    counter();  // 输出:函数被调用了 2 次
    counter();  // 输出:函数被调用了 3 次
    return 0;
}
//仅限于定义它的源文件内,内部链接,其他源文件不能访问


// file1.c
static int staticGlobalVar = 100;  // 静态全局变量

void function1() {
    printf("staticGlobalVar = %d\n", staticGlobalVar);
}

// file2.c
extern int staticGlobalVar;  // 错误:无法访问file1.c中的静态全局变量

void function2() {
    // 无法访问staticGlobalVar
}

static用于函数:改变函数的链接属性为内部链接,使其只能在本文件中被调用,其他源文件无法访问。

  • 优点包括:
  • 隐藏实现细节,仅暴露必要接口;
  • 防止不同文件中出现命名冲突;
  • 提升代码安全性,减少误调用风险。
// file1.c
static void privateFunction() {  // 静态函数
    printf("这是一个私有函数\n");
}

void publicFunction() {  // 普通函数
    privateFunction();  // 可以在同一文件中调用
}

// file2.c
extern void privateFunction();  // 错误:无法访问file1.c中的静态函数

void anotherFunction() {
    // privateFunction();  // 错误:无法调用
}

extern:用来声明一个在其他源文件中已经定义的变量或函数。

  • 对于变量,extern表明该变量已在别处定义,当前仅为声明。
// file1.c
int globalVar = 100;  // 定义全局变量

// file2.c
extern int globalVar;  // 声明外部变量

void function() {
    printf("globalVar = %d\n", globalVar);
    globalVar = 200;  // 修改全局变量
}
  • 对于函数,虽然函数默认就是外部链接,但使用extern可显式说明其来自外部源文件。

extern函数的作用:

  • 明确标识函数来源于其他文件;
  • 增强代码可读性;
  • 解决前向引用问题,允许在定义前使用函数。
// file1.c
void utilityFunction() {
    printf("这是一个实用函数\n");
}

// file2.c
extern void utilityFunction();  // 声明外部函数(可以省略extern)

void anotherFunction() {
    utilityFunction();  // 调用在file1.c中定义的函数
}

register:建议编译器将变量存入CPU寄存器以加快访问速度。尽管现代编译器已具备智能优化能力,此关键字的实际影响较小,但在嵌入式开发(如单片机)中仍有应用价值。

void function() {
    register int counter;  // 建议将counter存储在寄存器中
    for(counter = 0; counter < 1000; counter++) {
        // 频繁访问counter
    }
}//单片机里会遇到

const(重点):用于声明不可修改的常量。一旦初始化,其值不能被更改。

const int MAX_SIZE = 100;
// MAX_SIZE = 200;  // 错误:不能修改const变量

const与指针结合使用

  • 常量指针:指向的内容不可通过该指针修改,形式为 const int* pint const* p
const int *p;
  • 指针常量:指针本身不能改变所指向的地址,形式为 int* const p
int * const p;
  • 指向常量的常量指针:既不能修改指针指向,也不能通过指针修改内容,形式为 const int* const p
//内存和地址都不能修改
const int * const p;

const用于函数参数:表示函数不会修改传入的实际参数,常用于传递指针或引用类型,提高程序安全性和清晰度。

void printArray(const int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
        // arr[i] = 0;  // 错误:不能修改const数组元素
    }
    printf("\n");
}

const用于函数返回值:当函数返回一个对象或指针时,加上const可防止返回值被修改。

const char* getVersion() {
    return "v1.0.0";  // 返回一个不应被修改的字符串
}

内联函数(inline)

C99标准引入了inline关键字,旨在通过将函数体直接插入调用点来消除函数调用开销。

  • 适用于短小且频繁调用的函数;
  • 只是对编译器的建议,是否内联由编译器决定;
  • 可能增加最终生成的代码体积;
  • 避免了压栈、跳转和返回等操作,提升执行效率。
#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;
}

内联函数 vs 宏定义

  • 类型安全:内联函数参与类型检查,而宏不进行类型验证;
  • 求值次数:内联函数参数只计算一次,宏可能导致多次求值;
  • 支持复杂结构:内联函数可包含局部变量、条件判断和循环,宏则受限较多。

动态内存管理函数

malloc - 分配内存

  • 功能:按字节分配指定大小的内存空间;
  • 参数size_t size —— 请求的字节数;
  • 返回值:成功时返回指向新分配内存的指针,失败返回NULL;
  • 注意:分配的内存未初始化。
void *malloc(size_t size);
size

realloc - 调整内存大小

  • 功能:重新调整之前分配的内存块大小;
  • 参数
    • void* ptr —— 原内存块指针(若为NULL,等价于malloc);
    • size_t new_size —— 新的大小(若为0且ptr非空,等价于free);
  • 返回值:指向新内存的指针,失败返回NULL;
  • 注意
    • 若新内存更大,新增部分未初始化;
    • 若返回的新指针不同于原ptr,原内存已被自动释放。
void *realloc(void *ptr, size_t size);
ptr
#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 - 释放内存

  • 功能:释放由malloc、calloc或realloc分配的内存;
  • 参数void* ptr —— 待释放内存的指针(若为NULL,函数无操作);
  • 返回值:无;
  • 注意:释放后不应再访问该内存区域,否则引发未定义行为。
void free(void *ptr);

内存布局概览(面试重点)

堆区:用于动态内存分配,由程序员通过malloc/free手动管理,内存向高地址方向扩展。

栈区:存放局部变量、函数参数及返回地址,系统自动管理,生长方向为低地址。

图示展示了C语言程序典型的内存分区结构,理解各区域特性对掌握程序运行机制至关重要。

只能释放通过 malloc、calloc 或 realloc 分配的内存空间。

释放后的内存区域将不再有效,任何后续访问行为都是非法的。

同一块内存不允许被重复释放,否则会导致“双重释放”错误,可能引发程序崩溃或安全漏洞。

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!");  // 正确:分配了足够的空间

常见的内存操作问题包括:非法访问已释放内存、重复释放内存块,以及释放非动态分配的内存等。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

栏目导航
热门文章
推荐文章

说点什么

分享

扫码加好友,拉您进群
各岗位、行业、专业交流群