深入理解C语言指针(一):内存、地址与传址调用

深入理解C语言指针(一):内存、地址与传址调用

1. 内存与地址

内存可以看成由连续字节组成的空间,每个字节都有唯一地址。常见单位:

1
2
3
4
1 Byte = 8 bit
1 KB = 1024 Byte
1 MB = 1024 KB
1 GB = 1024 MB

变量占用若干连续字节,取地址操作符 & 可以获得其首地址。

1
2
int value = 10;
printf("%p\n", (void*)&value);

2. 指针变量

指针变量专门用于保存地址:

1
2
3
4
int value = 10;
int *p = &value;
printf("%d\n", *p);
*p = 20;
  • p:保存地址;
  • &value:取得 value 的地址;
  • *p:访问该地址处的 int 对象,也称解引用。

3. 指针类型的意义

指针类型决定两件事:

  1. 解引用时按什么类型解释内存;
  2. 指针加减时一次跨过多少字节。
1
2
3
int arr[3] = {10, 20, 30};
int *p = arr;
printf("%d\n", *(p + 1)); // 20

p + 1 前进 sizeof(int) 字节,而不是只前进1字节。

4. const修饰指针

4.1 指向常量的指针

1
const int *p = &value;

不能通过 p 修改对象,但 p 可以改为指向其他对象。

4.2 常量指针

1
int *const p = &value;

p 保存的地址不能改变,但可以通过 p 修改对象。

4.3 指向常量的常量指针

1
const int *const p = &value;

地址和指向的数据都不能通过 p 修改。

5. 指针运算

合法且常见的运算包括:

  • 指针加减整数;
  • 同一数组内两个指针相减;
  • 同一数组范围内比较指针;
  • 解引用和下标访问。

两个指针相减得到相隔的元素数量,结果类型为 ptrdiff_t。不要对互不相关的对象进行指针减法或大小比较。

6. 野指针与空指针

野指针指向无效或未知位置,常见来源:

  • 指针未初始化;
  • 指向的局部变量已经离开作用域;
  • 动态内存释放后仍继续使用;
  • 指针越过合法数组范围后被解引用。
1
2
3
int *p = NULL;
if (p != NULL)
printf("%d\n", *p);

初始化为空指针不能自动保证安全,但能使状态明确、便于判断。

7. assert断言

1
2
3
4
5
6
7
#include <assert.h>

void print_value(const int *p)
{
assert(p != NULL);
printf("%d\n", *p);
}

断言用于检查程序内部“不应被破坏”的前置条件。发布模式下可能因 NDEBUG 被移除,因此不能用断言代替用户输入校验和正常错误处理。

8. 传址调用

C语言所有参数都是按值传递。所谓传址,是把地址值复制给形参,通过这个地址间接修改原对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}

int main(void)
{
int x = 10, y = 20;
swap(&x, &y);
return 0;
}

9. 总结

指针就是“带有类型信息的地址”。理解指针时始终回答三个问题:它保存了谁的地址、指向的对象是否仍有效、解引用时应按什么类型读取。