在 c/c++ 中, 每个variable/object(变量)在源码层面和运行时层面具有以下核心属性:
identifier / name(标识符/变量名)
源代码中的标识符, 是程序员为可读性而赋予内存块的逻辑符号
仅存在于编译期(源码和编译器的符号表中)
在编译和链接完成后, 最终生成的机器码中不存在变量名, 它会被编译器完全替换为具体内存地址或寄存器偏移量
变量名在运行时不占用任何内存空间
address(变量地址)
变量在进程虚拟内存空间中的唯一编号(首地址)
存在于运行期, 它是操作系统分配和 CPU 寻址时真正使用的物理/虚拟坐标
value(变量值)
存储在该变量地址所标识的内存空间中的具体数据(底层为比特流)
CPU 根据变量数据类型(如 int 读 4 字节, double 读 8 字节), 从首地址开始读取相应长度比特流, 并将其解释为具体数值或字符
graph TB;
subgraph a[name: a]
V2(value: 1)
A2(address: 0x7ffa2ed1128)
end
graph LR;
%% 定义样式 (色彩分区)
classDef compile fill:#f9f2f4,stroke:#c7254e,stroke-width:2px,color:#333;
classDef runtime fill:#e8f4f8,stroke:#31708f,stroke-width:2px,color:#333;
classDef memory fill:#dff0d8,stroke:#3c763d,stroke-width:2px,color:#333;
classDef nodeStyle fill:#ffffff,stroke:#999,stroke-width:1px;
subgraph CompileTime ["🛠️ 编译期 (compile-time)"]
direction TB
Name["标识符 / 变量名 (identifier)<br/><code>age</code><br/><i>仅存在于源码与符号表<br/>不占运行时内存</i>"]
end
subgraph RunTime ["💻 运行期 (run-time / 虚拟内存空间)"]
direction TB
subgraph MemoryBlock ["内存实体 (memory object: <code>int age = 25;</code>)"]
direction TB
Addr["📍 变量地址 (address)<br/><code>0x7FFA...1288</code><br/><i>内存的首坐标 / 门牌号</i>"]
Type["📏 数据类型 (type)<br/><code>int</code> (4字节)<br/><i>决定读取长度与解释规则</i>"]
Val["📦 变量值 (value)<br/><code>25</code> (底层: <code>0x00000019</code>)<br/><i>按 type 解释后的具体数据</i>"]
end
end
%% 跨图连线与逻辑说明
Name -- "1. 符号解析与重定位<br/>(编译后标识符消亡,<br/>替换为地址/偏移量)" --> Addr
Addr -- "2. CPU 寻址定位<br/>(通过地址找到物理容器)" --> Type
Type -- "3. 类型解释<br/>(按类型读取比特流并解析)" --> Val
%% 源码层面的抽象访问 (虚线)
Name -. "4. 源码直接访问 (逻辑抽象)<br/>(底层自动转换为:<br/>地址寻址 + 类型解析)" .-> Val
%% 应用样式
class CompileTime compile;
class RunTime runtime;
class MemoryBlock memory;
class Name,Addr,Type,Val nodeStyle;
基于这些属性, 访问变量有两种方式:
- 直接访问(
direct access): 通过标识符读写数据
编译器在底层会将其解析为该标识符绑定的内存位置, 并生成直接寻址或基于基址/寄存器的机器指令
- 间接访问(
indirect access): 通过变量所在的内存地址读写数据
编译器会生成间接寻址指令, 先读取地址, 再访问该地址指向的内存位置
概念
在日常交流中, “指针”一词常被混用, 但在严谨的 c/c++ 语境中, 必须区分类型与变量
pointer type(指针类型, 简称为指针)
本质上是一种复合数据类型(compound type), 与int char等基础类型平级
它定义该类型实例的内存布局(固定大小, 如 4 或 8 字节)以及允许的操作(如指针算术、解引用)
pointer variable(指针变量)
是指针类型的实例化对象, 与int变量 char变量一样
与普通变量存储具体数据值(如整数、字符)不同, 指针变量存储的值(value)是另一个目标对象的内存地址(或空指针值 nullptr/NULL)
int *p_int; // p_int 是一个指针, 专门存储 int 类型变量的地址
char *p_char; // p_char 是一个指针, 专门存储 char 类型变量的地址
double *p_double; // p_double 是一个指针, 专门存储 double 类型变量的地址
日常使用中一般将
pointer vaiable(指针变量)简称为pointer(指针)
- 指向关系(points-to)
当一个指针变量的值等于某个普通变量的内存地址时, 称该指针变量指向(points to)该普通变量
- 双重属性
指针变量本身也是一个变量(对象), 因此它同样具备标识符和内存地址, 并占用独立的内存空间
指针变量的值: 存储的是目标变量的内存地址
指针变量的地址: 是指针变量自身在内存中的物理/虚拟位置
graph LR;
subgraph a["普通变量 a(int a = 1)"]
V2(value: 1)
A2(address: 0x1000)
end
subgraph p["指针变量 p(int* p = &a)"]
V1(value: 0x1000)
A1(address: 0x2000)
end
V1 -->|p 的值是 a 的地址| A2
通过指针, 可间接访问或修改指针所指向变量的值(称为解引用), 而无需知道或依赖原变量名
基本操作
取地址(address of)
&运算符用于获取变量地址, 通常用于初始化指针变量
#include <stdio.h>
int main() {
int a = 1;
// 将 a 的地址赋值给指针变量 p
int *p = &a;
// pointer p value = 0x7ffa2ed1128
// variable a address = 0x7ffa2ed1128
printf("pointer p value = %p\n", p);
printf("variable a address = %p\n", &a);
return 0;
}
graph LR;
subgraph a[int a]
V2(value: 1)
A2(address: 0x7ffa2ed1128)
end
subgraph p[int* p]
V1(value: 0x7ffa2ed1128)
end
V1-->A2
解引用(dereference)
* 运算符可访问指针变量指向的内存位置(对象), 称为解引用
#include <stdio.h>
int main() {
int a = 1;
int *p = &a;
// a value = 1
// a address = 0x7ffc241ed608
printf("a value: %d\na address: %p\n", a, &a);
// p value = 0x7ffc241ed608
// *p value = 1
printf("p value: %p\n*p value: %d\n", p, *p);
return 0;
}
graph LR;
subgraph a[int a]
V2(value: 1)
A2(address: 0x7ffc241ed608)
end
subgraph p[int* p]
V1(value: 0x7ffc241ed608)
end
V1=="*解引用"==>A2
属性
指针变量指向(指针值)
指针变量的值就是它所存储目标变量的地址, 也称指针变量指向目标变量
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
// a = 10, &a = 0x7ffff423e928
printf("a = %d, &a = %p\n", a, &a);
// *p = 10, p = 0x7ffff423e928
printf("*p = %d, p = %p\n", *p, p);
return 0;
}
graph LR;
subgraph a[int a]
V2(value: 10)
A2(address: 0x7ffff423e928)
end
subgraph p[int* p]
V1(value: 0x7ffff423e928)
end
V1-->A2
改变指针变量指向
让指针变量存储一个新变量的地址, 称为改变指针变量的指向
#include <stdio.h>
int main() {
int a = 255;
int b = 170;
int* p = &a;
// p = 0x7fff62e598a8, *p = 255
printf("p = %p, *p = %d\n", p, *p);
// 改变指针指向
p = &b;
// p = 0x7fff62e5989c, *p = 170
printf("p = %p, *p = %d\n", p, *p);
return 0;
}
graph LR;
subgraph S2[int b]
direction LR
V2(value: 170)
A2(address: 0x7fff62e5989c)
end
subgraph S1[int a]
direction LR
V1(value: 255)
A1(address: 0x7fff62e598a8)
end
subgraph S3[int* p]
V3(value: 0x7fff62e598a8)
V4(value: 0x7fff62e5989c)
end
V3-.①.->A1
V4--②-->A2
间接修改变量值
通过指针变量存储的地址可以间接修改其指向变量的值
#include <stdio.h>
int main() {
int a = 255;
int* p = &a;
// p = 0x7ffc9fcdabf8, *p = 255
// a = 255, &a = 0x7ffc9fcdabf8
printf("p = %p, *p = %d\n", p, *p);
printf("a = %d, &a = %p\n",a, &a);
*p = 1;
// p = 0x7ffc9fcdabf8, *p = 1
// a = 1, &a = 0x7ffc9fcdabf8
printf("p = %p, *p = %d\n", p, *p);
printf("a = %d, &a = %p\n",a, &a);
return 0;
}
graph LR;
subgraph S1[int* p]
V1(value: 0x7ffc9fcdabf8)
end
subgraph S2[int a]
direction LR
V2(value: 255)
A2(address: 0x7ffc9fcdabf8)
end
V3(value: 1)
V1-->A2
V2==>V3
类型
指针变量去掉变量名后是指针类型
| 声明语句 | 指针类型 | 指针指向类型 | 说明 |
|---|---|---|---|
int *p1 |
int * |
int |
决定解引用时读取 4 字节并按整数解释 |
char *p2 |
char * |
char |
决定解引用时读取 1 字节并按字符解释 |
double **p3 |
double ** |
double * |
指向类型本身也是一个指针 |
因为无论何类型指针都是存储地址值, 所以指针变量大小只与系统位数有关, 与类型无关
32位系统指针大小为4字节, 64位系统指针大小位8字节
#include <stdio.h>
int main() {
int* p = NULL;
double* p1 = NULL;
printf("sizeof p = %d\nsizeof p1 = %d\n", sizeof(p), sizeof(p1));
return 0;
}
指针操作的内存范围(步长与边界)
指针类型本身不包含连续内存的长度信息(数组指针除外)
指针变量只知道“指向类型的单个对象大小”, 并以此作为指针算术(如 p++)的步长
// 指针变量指向4字节大小区域
int a = 1;
int *p = &a;
当指针变量指向动态分配的连续内存时, 指向区域的实际大小是由程序员的业务逻辑保证的, 编译器和指针类型本身不进行边界检查
// 指针变量指向1024 字节 大小区域
const int SIZE = 1024;
char *p =(char *)malloc(sizeof(char) * SIZE);
特殊指针变量
pointer to const (指向常量的指针)
A pointer to a constant object.
指针指向的对象是常量
不能通过该指针间接修改目标变量的值(指向的值不可变), 但指针本身可以改变指向
const int *p
// 或
int const *p
指针类型 int *, 指针指向类型 const int
graph LR;
subgraph S1[const int a]
direction LR
V(value: 不可变)
A(address)
end
subgraph S2[const int* p]
V2(value)
direction LR
end
V2-->A===V
#include <stdio.h>
int main(void) {
const int a = -1;
const int b = 1;
const int *p = &a;
printf("%d\n", *p);
// 尝试改变指针指向
p = &b;
printf("%d\n", *p);
// 尝试改变指针指向值, 报错
*p = -1;
printf("%d\n", *p);
return 0;
}
运行时报错
error: read-only variable is not assignable
const pointer (常量指针)
A constant pointer to an object.
指针本身是常量, 指针的指向在初始化后不可更改(指向不可变), 但可以通过该指针修改目标变量的值(指向的值可变)
int *const p;
指针类型 int *const, 指针指向类型 int
graph LR;
subgraph S1[int a]
V(value)
A(address)
end
subgraph S2[int *const p]
direction LR
V2(value: 不可变)
end
V2==绑定===A
#include <stdio.h>
int main(void) {
int a = -1;
int *const p = &a;
printf("%d\n", *p);
// 尝试改变指针指向值
*p = 1;
printf("%d\n", *p);
int b = 1;
// 尝试改变指针指向, 报错
p = &b;
printf("%d\n", *p);
return 0;
}
运行时报错
error: cannot assign to variable 'p' with const-quakufued type 'int *const'
note: variable 'p' declared const here
function pointer(函数指针)
指向函数的指针, 存储函数入口地址, 通过函数指针可间接调用函数
返回值(*)(参数...,)
#include <stdio.h>
int get_max(int x, int y) {
return x > y ? x : y;
}
int main() {
int(*p)(int, int) = NULL;
p = get_max;
// 2
printf("%d\n", p(1, 2));
return 0;
}
array of pointers(指针数组)
指针数组是数组, 数组中元素为指针
int *p[3];
指针类型 int *, 指针指向类型 int
#include <stdio.h>
int main(void) {
int *p[3];
int a = 1;
int b = 2;
int c = 3;
p[0] = &a;
p[1] = &b;
p[2] = &c;
for(int i = 0; i < 3; i++) {
// p[0] = 1, &p[0] = 0x7fffa48dbfd0
// p[1] = 2, &p[1] = 0x7fffa48dbfd8
// p[2] = 3, &p[2] = 0x7fffa48dbfe0
printf("p[%d] = %d, &p[%d] = %p\n", i, *p[i], i, &p[i]);
}
return 0;
}
pointer to pointer(二级指针)
指向指针的指针, 其值是另一个指针变量的地址。常用于在函数内部修改外部指针的指向, 或表示二维数组
int **a;
graph LR;
subgraph S1[变量]
direction LR
V1(value: ...)
A1(address: ...)
end
subgraph S2[一级指针]
direction LR
V2(value: ...)
A2(address: ...)
end
subgraph S3[二级指针]
direction LR
V3(value: ...)
end
V3-->A2
V2-->A1
#include <stdio.h>
int main() {
int a = 1;
int *p = &a;
int **sp = &p;
// a: 1
// &a: 0x7ffd79d476d8
printf("a: %d\n, &a: %p\n\n", a, &a);
// *p: 1
// p: 0x7ffd79d476d8
// &p: 0x7ffd79d476d0
printf("*p: %d\n, p: %p\n, &p: %p\n\n", *p, p, &p);
// **sp: 1
// *sp: 0x7ffd79d476d8
// sp: 0x7ffd79d476d0
printf("**sp: %d\n, *sp: %p\n, sp: %p\n", **sp, *sp, sp);
return 0;
}
graph LR;
subgraph S1[int a]
direction LR
V1(value: 1)
A1(address: 0x7ffd79d476d8)
end
subgraph S2[int* p]
direction LR
V2(value: 0x7ffd79d476d8)
A2(address: 0x7ffd79d476d0)
end
subgraph S3[int** sp]
direction LR
V3(value: 0x7ffd79d476d0)
end
V3-->A2
V2-->A1
pointer to array (数组指针)
是一个指针, 指向一个完整的数组(而非数组的首元素)
常用于多维数组的传参
int(*p)[3];
指针类型 int *, 指针指向类型 int [3]
#include <stdio.h>
int main(void) {
int a[3] = {1, 2, 3};
int(*p)[3] = &a;
for(int i = 0; i < 3; i++) {
// &a[0] = 0x7ffdeaf17b0, a[0] = 1
// &a[1] = 0x7ffdeaf17b4, a[1] = 2
// &a[2] = 0x7ffdeaf17b8, a[2] = 3
printf("&a[%d] = %p, a[%d] = %d\n", i, &a[i], i, a[i]);
}
for(int i = 0; i < 3; i++) {
//(*p + 0) = 0x7ffdeaf17b0, *(*p + 0) = 1
//(*p + 1) = 0x7ffdeaf17b4, *(*p + 1) = 2
//(*p + 2) = 0x7ffdeaf17b8, *(*p + 2) = 3
printf("(*p + %d) = %p, *(*p + %d) = %d\n", i,(*p + i), i,*(*p + i));
}
return 0;
}
graph LR;
subgraph S1["int a[]"]
subgraph A1[a0]
V0("value")
A0("address")
end
subgraph A2[a1]
V12("value")
A12("address")
end
end
subgraph S2["int(*p)[]"]
V2(value)
direction LR
end
A0-->A12
V2==>A0
pointer to object (对象指针)
在 c++ 或 c 结构体中, 指向类或结构体实例的指针
除了使用 * 解引用外, 还可使用 -> 运算符来简化成员访问
#include <stdio.h>
struct Point {
int x;
int y;
};
int main() {
struct Point pt = {10, 20};
struct Point *p_pt = &pt;
// 使用 -> 运算符访问对象成员, 等价于(*p_pt).x
printf("x = %d, y = %d\n", p_pt->x, p_pt->y);
return 0;
}