address 在 C 语言中是一个基础但至关重要的概念,它指的是内存中某个字节单元的唯一编号,理解地址是理解指针、内存管理和数据在计算机中如何被处理的关键。

什么是地址?
想象一下计算机的内存就像一个巨大的酒店,每个酒店房间都有一个唯一的房间号(101, 102, 103...),在 C 语言中:
- 内存 = 这个大酒店
- 变量 = 入住的客人(比如张三、李四)
- 地址 = 客人入住的房间号
当你创建一个变量时,C 编译器会为它在内存中分配一个或多个连续的“房间”(字节),并记录下这个“房间号”(地址)。
关键点:
- 唯一性:每个字节在内存中都有一个唯一的地址。
- 数值性:地址本质上是内存单元的编号,是一个无符号整数。
- 不可预测性:你无法预先知道一个变量会被分配到哪个具体的地址,这个地址由操作系统和编译器在程序运行时动态决定。
如何获取变量的地址?—— & 运算符
在 C 语言中,我们使用取地址运算符 & 来获取一个变量在内存中的地址。

& 运算符:
- 作用:返回其操作数的内存地址。
- 操作数:必须是变量(或数组元素、结构体成员等在内存中占有实际空间的实体)。
- 不能用于:常量、表达式(如
a + b)或函数名(函数名会“衰变”为其地址,但这是特殊情况)。
示例代码:
#include <stdio.h>
int main() {
int age = 25; // 定义一个整型变量 age
double price = 99.99; // 定义一个双精度浮点型变量 price
// 使用 %p 格式化输出地址,%p 表示以十六进制形式打印指针
printf("age 的地址是: %p\n", &age);
printf("price 的地址是: %p\n", &price);
return 0;
}
可能的输出(地址每次运行都可能不同):
age 的地址是: 0x7ffc8a1b7a7c
price 的地址是: 0x7ffc8a1b7a70
分析:
&age获取了变量age的内存地址。&price获取了变量price的内存地址。- 从输出可以看出,
price的地址比age的地址小,这是因为price是一个double类型(通常占 8 字节),而age是一个int类型(通常占 4 字节),在内存分配时,price可能被分配在age的前面。 - 地址通常以十六进制(Hexadecimal)形式表示,
0x7ffc8a1b7a7c。
为什么地址很重要?—— 指针
地址本身虽然有用,但它的真正威力在于与指针的结合。

指针:
- 定义:一个专门用来存储内存地址的变量。
- 本质:它也是一个变量,但它存放的不是普通数据(如整数、浮点数),而是另一个数据的地址。
指针的声明和使用:
指针的声明需要在其前面加上 号。
int age = 25; int *ptr_to_age; // 声明一个指向整型的指针,名为 ptr_to_age ptr_to_age = &age; // 将 age 的地址赋给指针 ptr_to_age
我们有了一个名为 ptr_to_age 的指针,它“指向”了 age 变量。
指针的核心操作:
-
解引用 / 间接寻址:使用 运算符来访问指针所指向地址的值。
printf("通过指针 ptr_to_age 获取的值是: %d\n", *ptr_to_age); // 输出 25 *ptr_to_age = 30; // 通过指针修改 age 的值 printf("age 的值是: %d\n", age); // 输出 30这里的 是解引用运算符,与声明时的 含义不同。
-
获取指针本身的地址:指针本身也是一个变量,它也有自己的内存地址。
printf("指针 ptr_to_age 自身的地址是: %p\n", &ptr_to_age);
地址与数据类型的关系
指针必须声明其指向的数据类型(如 int*, char*, double*),这有两个原因:
-
解引用时的数据大小:当使用 解引用指针时,编译器需要知道要从该地址开始读取多少个字节。
int *p;解引用时,读取 4 个字节(假设int是 4 字节)。double *p;解引用时,读取 8 个字节。char *p;解引用时,读取 1 个字节。
-
指针运算:当对指针进行加减运算时(如
p++),编译器会根据它指向的数据类型来移动正确的字节数。p是一个int*,p++会让p的值增加sizeof(int)(4)。p是一个double*,p++会让p的值增加sizeof(double)(8)。
示例:
int arr[3] = {10, 20, 30};
int *p = arr; // 数组名 arr 会“衰变”为其首元素的地址
printf("p 指向的地址: %p, 值: %d\n", p, *p);
p++; // p 的地址增加了 sizeof(int) 字节
printf("p++ 后的地址: %p, 值: %d\n", p, *p);
输出(假设 int 为 4 字节):
p 指向的地址: 0x... , 值: 10
p++ 后的地址: 0x... (比上一个地址大 4), 值: 20
特殊地址
-
NULL指针:值为0的指针,它是一个特殊的指针值,表示“不指向任何有效的内存对象”,永远不应该对NULL指针进行解引用操作,否则会导致未定义行为,通常是程序崩溃(段错误)。int *ptr = NULL; // if (ptr != NULL) { *ptr = 100; } // 安全的做法 // *ptr = 100; // 危险!会导致崩溃 -
*空指针常量 `(void)0
**:在 C 标准库中,NULL通常被定义为(void*)0`,表示一个通用的、不指向任何类型的指针。
地址的实际应用
-
函数参数传递(按引用调用):C 语言默认是“按值传递”,意味着函数内部无法修改外部变量的值,但通过传递变量的地址,函数就可以修改外部变量。
void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main() { int x = 5, y = 10; swap(&x, &y); // 传递 x 和 y 的地址 printf("x = %d, y = %d\n", x, y); // 输出 x = 10, y = 5 return 0; } -
动态内存分配:使用
malloc(),calloc(),realloc()等函数在程序运行时从堆上分配内存,这些函数返回分配的内存块的起始地址,你需要用一个指针来接收这个地址。int *dynamic_array = (int*) malloc(10 * sizeof(int)); // 分配 10 个 int 的空间 if (dynamic_array != NULL) { dynamic_array[0] = 100; // ... free(dynamic_array); // 使用完毕后必须释放内存 } -
高效处理大型数据结构:当向函数传递一个大型结构体或数组时,按值传递会复制整个数据,非常耗时且消耗内存,传递其地址(一个指针)则高效得多,只复制一个地址的值。
-
直接操作硬件:在嵌入式系统或操作系统开发中,需要直接读写特定的内存地址(如硬件寄存器),这时就需要用到地址和指针。
| 概念 | 符号/术语 | 描述 |
|---|---|---|
| 地址 | 内存地址 | 内存中每个字节的唯一编号。 |
| 取地址 | & (Address-of) |
获取一个变量的内存地址。 |
| 指针 | int *p; |
一个存储内存地址的变量。 |
| 解引用 | (Dereference) | 通过指针访问其指向地址的值。 |
| 指针类型 | int*, char* |
指针指向的数据类型,决定了解引用时的数据大小和指针运算的步长。 |
| 特殊地址 | NULL |
值为 0 的指针,表示不指向任何有效对象。 |
掌握地址和指针是 C 语言进阶的必经之路,它们虽然概念上有些抽象,但通过多写代码、多调试(观察地址的变化),你会逐渐熟悉并熟练运用它们。
