## C语言编译过程
-
预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法,将#include #define这些头文件内容插入到源码中
gcc -E main.c -o main.i
-
编译:检查语法,将预处理后文件编译生成汇编文件,将预处理阶段的源代码转换为汇编语言
gcc -S main.i -o main.s
-
汇编:将汇编文件生成目标文件(二进制文件),将汇编语言转换为机器代码生成目标文件
gcc -c main.s -o main.o
-
链接:将目标文件链接为可执行程序,将多个目标文件和库文件链接在一起
gcc main.o -o main
## 进程的内存分布
- 程序运行起来(没有结束前)就是一个进程
- 对于C语言而言,内存空间主要由五个部分组成:代码区(text)、数据区(data)、未初始化数据区(bss),堆(heap) 和 栈(stack) ,有些人会把data和bss合起来叫做静态区或全局区

|----------------|------------------|------------------------------|
| 区域 | 加载内容 | 注意 |
| 代码区 | 可执行文件代码段 | 这块内存是不可以在运行期间修改的 |
| 未初始化数据区 | 可执行文件BSS段 | 存储在该区的数据生存周期为整个程序运行过程 |
| 全局初始化数据区/静态数据区 | 可执行文件数据段 | 存储在该区的数据生存周期为整个程序运行过程 |
| 堆区 | 动态内存分配,位于BSS和栈之间 | 由程序员分配和释放,程序员不释放程序结束时由操作系统回收 |
| 栈区 | 函数的参数值、返回值、局部变量等 | 由编译器自动分配释放,在程序运行过程中实时加载和释放 |
## 堆区内存使用
1、malloc
cs
#include <stdlib.h>
void *malloc(size_t size);
功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。
分配的内存空间内容不确定。
参数:
size:需要分配内存大小(单位:字节)
返回值:
成功:分配空间的起始地址
失败:NULL
2、free
cs
#include <stdlib.h>
void free(void *ptr);
功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域的首地址。
对同一内存空间多次释放会出错。
参数:
ptr:需要释放空间的首地址,被释放区应是由malloc函数所分配的区域。
返回值:无
例子:
cs
#include <stdlib.h>
#include <stdio.h>
int main() {
int i, *arr, n;
printf("请输入要申请数组的个数: ");
scanf("%d", &n);
// 堆区申请 n * sizeof(int) 空间,等价int arr[n]
arr = (int *)malloc(n * sizeof(int));
if (arr == NULL) { // 如果申请失败,提前中断函数
printf("申请空间失败!\n");
return -1;
}
for (i = 0; i < n; i++){
// 给数组赋值
arr[i] = i;
}
for (i = 0; i < n; i++) {
// 输出数组每个元素的值
printf("%d, ", *(arr+i));
}
// 释放堆区空间
free(arr);
return 0;
}
## 内存分布代码分析
|--------|-------|--------------------|--------------------|
| 类型 | 存储位置 | 生命周期 | 初始值 |
| 普通局部变量 | 栈区 | 函数执行完毕后销毁 | 每次函数调用都会被初始化初始值不确定 |
| 静态局部变量 | 静态存储区 | 整个程序运行时间,函数运行结束仍保留 | 第一次函数调用时被初始化直到程序结束 |
cs
#include <stdio.h>
void normal_func() {
int i = 0;
i++;
printf("局部变量 i = %d\n", i);
}
void static_func() {
static int j = 0;
j++;
printf("static局部变量 j = %d\n", j);
}
int main() {
// 调用3次normal_func()
normal_func();
normal_func();
normal_func();
// 调用3次static_func()
static_func();
static_func();
static_func();
return 0;
}
运行结果:
cs
局部变量 i = 1
局部变量 i = 1
局部变量 i = 1
static局部变量 j = 1
static局部变量 j = 2
static局部变量 j = 3
返回堆区地址:
cs
#include <stdio.h>
#include <stdlib.h>
int *func() {
int *tmp = NULL;
// 堆区申请空间
tmp = (int *)malloc(sizeof(int));
*tmp = 100;
return tmp; // 返回堆区地址,函数调用完毕,不释放
}
int main() {
int *p = NULL;
p = func();
printf("*p = %d\n", *p); // ok
// 堆区空间,使用完毕,手动释放
if (p != NULL) {
free(p);
p = NULL;
}
return 0;
}
-
在
func
函数中,通过malloc
在堆区动态分配了一个int
类型的内存空间,并将其地址赋值给指针tmp
。 -
将值
100
存储到分配的内存中。 -
返回指向堆区内存的指针
tmp
。由于堆区内存是在程序运行时动态分配的,它不会在函数返回时自动释放,因此返回的指针是有效的 -
在
main
函数中,调用func
函数并将返回的指针赋值给p
。 -
通过
printf
输出*p
的值,此时*p
指向的内存是有效的,因此可以正常输出100
。 -
使用
free
函数释放动态分配的内存,避免内存泄漏。 -
将
p
设置为NULL
,这是一个良好的编程习惯,可以避免后续误用已释放的指针。
## 如何修复野指针
cs
错误代码:
#include <stdio.h>
int main() {
int *ptr;
int num = 10;
*ptr = num;
printf("Value: %d\n", *ptr);
return 0;
}
cs
修改代码:
#include <stdio.h>
int main() {
int *ptr = NULL;
int num = 10;
ptr = #
printf("Value: %d\n", *ptr);
return 0;
}
## 接受一个整数作为参数,计算并返回它的阶乘值
cs
void hanshu(int a)
{
int sum = 1;
while(a)
{
sum *= a;
a--;
}
printf("a的阶乘是: %d",sum);
}
int main()
{
int a;
printf("请输入一个整数:");
scanf("%d",&a);
hanshu(a);
return 0;
}
## 定义一个整型变量和一个指向该变量的指针,并将指针指向变量的地址,通过2种方式打印整型变量的内容
cs
int main() {
int a =250;
int* p1 = &a;
printf("a-address: %p\n",&a);
printf("a-address: %p\n",p1);
return 0;
}
## 定义一个整型变量,初始值为100,通过某个函数修改改变量的内容为123
cs
#include <stdio.h>
void func(int* p){
*p = 123;
}
int main() {
int a = 100;
printf("a = %d\n",a);
func(&a);
printf("a = %d\n",a);
return 0;
}