C语言程序的内存分布和指针简介

C语言程序的内存分布

1364115879_7087.jpg

代码区

只读区域,程序运行过程中无法做任何修改的存储区域,用于存放CPU执行的机器指令

通常,代码区是可共享的(即另外的执行程序可以调用它),因为对于频繁被执行的程序,只需要在内存中有一份代码即可。

代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。

数据区

包括已初始化的数据段(.data)和未初始化的数据段(.bss)

已初始化的数据段:通常简称为数据段,是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。数据段中的静态数据区存放的是程序中已初始化的全局变量、静态变量和常量

未初始化的数据段 :亦称BSS区,存入的是全局未初始化变量。BSS区的数据在程序开始执行之前被内核初始化为0。

堆区

用于动态内存分配。

当进程调用malloc,calloc,realloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

栈区

由编译器自动分配释放内存的区间,所得的内存空间一般都是连续的,是用来存放函数的参数值、局部变量的值、函数的返回值等。

在程序运行时由编译器在需要的时候分配,在不需要的时候自动清除。

指针

阅读代码,思考该程序的输出是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 #include <stdio.h>

void swap(int x, int y);

int main()
{
int a = 10, b = 20;

swap(a, b);
printf("a=%d\nb=%d\n", a, b);

return 0;
}
void swap(int x, int y)
{
int t;
t = x;
x = y;
y = t;
}

内存与地址

在计算机中,数据是存放在内存单元中的,一般把内存中的一个字节称为一个内存单元。为了更方便地访问这些内存单元,可预先给内存中的所有内存单元进行地址编号,根据地址编号,可准确找到其对应的内存单元。由于每一个地址编号均对应一个内存单元,因此可以形象地说一个地址编号就指向一个内存单元。C 语言中把地址形象地称作指针。

C语言中的每个变量均对应内存中的一块内存空间,而内存中每个内存单元均是有地址编号的。在 C 语言中,可以使用运算符 & 求某个变量的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main(void)
{
char c='A';
int a=100;
printf("a=%d\n",a);//输出变量a的值
printf("&a=%x\n",&a);//输出变量a的地址
printf("c=%c\n",c);
printf("&c=%x\n",&c);
return 0;
}


1
2
3
4
5
//某次运行的结果:
a=100
&a=12ff40
c=A
&c=12ff44

区分变量的地址值变量的值。如上例中,变量 a 的地址值(指针值)为12ff40,而变量 a 的值为 100。

IMG_0037(20201024-223351).PNG

屏幕截图(52).png

一个指针是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。但是常把指针变量简称为指针。为了避免混淆,我们约定:“指针” 是指地址,是常量,“指针变量”是指取值为地址的变量。

指针名、数组名、函数名就是地址,它们分别表示指针所指向元素的地址、数组的首地址和函数的入口地址

指针变量的 定义格式 为:

数据类型符 * 变量名;

如:

1
int *p1; 

指针变量初始化的方法:

1
2
int a;  
int *p = &a; //&为取址运算符,&a即为取a的地址

引用指针变量

1
2
3
int a; 
int *p = &a; // p指向a ,相当于*p就是a的别名
*p = 10; // 相当于 a = 10;

void*类型指针

表示形式为 void*p;表示不指定 p 是指向哪一种数据类型的指针变量。使用时要进行强制类型转换。例如:

1
2
3
4
5
6
7
char *p1; 

void *p2;

p1 = (char *)p2;

p2 = (void *)p1;

空指针NULL

在C语言中,如果一个指针不指向任何数据,我们就称之为空指针,用NULL

表示。例如:

1
int *p = NULL;

注意区分大小写,null 没有任何特殊含义,只是一个普通的标识符。

NULL 是一个宏定义,在stdio.h被定义为:

1
#define NULL ((void *)0)

我们知道,变量一旦定义就要分配内存,指针变量也是如此。例如:

1
int *p;  //它不是空指针

它的值是随机的,是垃圾值,如果不小心使用了它,运行时一般会引起段错误,导致程序退出,甚至会不知不觉地修改数据。

所以,为了实现交换两个数的值的功能,可以将形参改为接收实参的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 #include <stdio.h>

void swap(int *x, int *y);

int main()
{
int a = 10, b = 20;

swap(&a, &b);
printf("a=%d\nb=%d\n", a, b);

return 0;
}
void swap(int *x, int *y)
{
int t;
t = *x;
*x = *y;
*y = t;
}

malloc 与 free

malloc


头文件stdlib

原型void malloc(size_t size)

所以需要根据实际你需要的类型对其强制类型转换

返回值

成功时,返回指向新分配内存的指针。
失败时,返回空指针(NULL)

参数:size : 要分配的字节数

free


头文件stdlib
原型void free( void* ptr );
参数:指向要解分配的内存的指针
返回值:无

我们需要一个大小为 N ( N < 1000)的数组,我们可能会想写成arr[N],由输入决定数组的大小,但是这样写编译会出错。

所以我们通常这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(void) {

int arr[1000] = { 0 };
int N = 0;
int i = 0;

printf("请输入数组的大小\n");
scanf("%d", &N);

printf("请输入%d个数\n", N);
for (i = 0; i < N; i++)
scanf("%d", &arr[i]);

return 0;
}

但是这样的不确定可能会造成空间的浪费。

其实我们可以这样写

1
int* arr = (int*)malloc(sizeof(int) * N)

该代码定义了一个指针arr指向sizeof(int) * N这么大的空间