数组-指针
总结:
定义:
数据类型 数组名称[长度];
//数组特性,空间连续,同类型数据, 数组名是数组首地址,第一个元素地址,常量,下标0-(n-1),越界。
数据类型 *指针变量=地址 ;
// 字节,地址, 基地址,指针就是地址,指针变量, 解引用(索引),指针运算。
数组:
多维数据;字符数组, 字符串数组(指针数组);变长数组;数组作为函数参数
指针:
多级指针;数组指针;数组与指针;const修饰指针
1.数组
1.数组及数组元素地址
⭐全局变量和局部变量的地址不在同一个区域。
假如有如下定义:int a[3][5]; 完成如下要求:
- 用1种方法表示 a[2][3] 的地址。
- 用2种完全等价的方法表示 a[2][0] 的地址。
- 用3种完全等价的方法表示 a[0][0] 的地址。
解析:
第一问直截了当,a[2][3]的地址:
&a[2][3]
第二问中,除了可以直接取a[2][0]的地址,子数组a[2]本质上也是其首元素a[2][0]的地址:
&a[2][0]
a[2]
第三问中,除了可以直接取a[0][0]的地址和子数组a[0]之外,还可以利用数组下标的运算规则,将下标运算符去掉:
&a[0][0]
a[0]
*a
提示:
a[0] 等价于 *(a+0) 等价于 *(a) 等价于 *a
2.变长数组
可以通过结构体,堆空间来实现变长数组
struct Node{
int length;
char data[0]; //数组越界4
};

int x=0, y=0;
printf("请输入数组的行和列:");
scanf("%d %d", &x, &y);
//定义变长数组
int arr[x][y];//⭐定义时不能初始化
for(int i=0; i<x; i++){
for(int j=0; j<y; j++){
arr[i][j] = random()%100;
printf("%d ", arr[i][j]);
}
printf("\n");
}
3.数组作为函数参数
数组作为函数参数, 是把数组的首地址传入函数 , (首地址--地址--指针)
⭐数组作为函数参数会被转换为指针来使用
void show(int arr[10])
{
printf("%d\n", sizeof(arr)); //8
}
int main(void){
int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%d\n", sizeof(arr)); //40
show(arr);
}
3.1 数组名称和指针变量区别
int a[5]={1,2,3,4,5};
int *p = a;
1.数组名是地址, 指针变量代表也是地址
2.可以进行运算, 数组名+1, 指针+1
3.可以解引用 *a, *p
4.数组名是常量 ,本身不能被修改,a++是不允许的,但是p++可以
5.数组名不能被赋值 a=0x123, 但是指针可以被赋值p=0x123
例子:
int a[5]= {1,2,3,4,5};
int *p = a;
printf("%d,%d",a[0], p[0]);
printf("%d,%d",*a, *p);
printf("%d,%d",*(a+0), *(p+0));
3.2 加法交换律与指针访问的关系
*(a+0) == a[0] 加法交换律得到 a+0 == 0+a.
*(a+0)是a[0], 那么可以得到 *(0+a)是0[a]
题目:
char a[3] = {'a', 'b', 'c'};
printf("%c", a[0]); //a
printf("%c", 1[a]); //b
2.指针
2.1. 基本概念
2.1.1 字节序
✅ 正确理解如下:
假设我们有一个 4 字节的整数(例如 0x12345678),它在内存中如何存储,取决于是 大端序 还是 小端序。
▶ 大端序(Big Endian)
- 高位字节(0x12)存放在低地址
- 低位字节(0x78)存放在高地址
| 地址(低 → 高) | 0x00 | 0x01 | 0x02 | 0x03 |
|---|---|---|---|---|
| 内容 | 0x12 | 0x34 | 0x56 | 0x78 |
🔸 直观理解:“数据的高位在前”
▶ 小端序(Little Endian)
- 低位字节(0x78)存放在低地址
- 高位字节(0x12)存放在高地址
| 地址(低 → 高) | 0x00 | 0x01 | 0x02 | 0x03 |
|---|---|---|---|---|
| 内容 | 0x78 | 0x56 | 0x34 | 0x12 |
🔸 直观理解:“数据的低位在前”
🚨 总结:
大端序:高位数据存储在低地址,低位数据存储在高地址。
小端序:低位数据存储在低地址,高位数据存储在高地址。
挺多人学二进制存储的时候都会问:为啥还要搞个大端小端之分?不能统一吗?
咱们来聊聊背后的原因:
🧠 本质原因:历史遗留 + 硬件架构设计不同
在计算机发展的早期,各家厂商(IBM、Intel、Motorola 等)自行设计硬件,数据怎么在内存中排布,完全由芯片设计者决定。于是就出现了两种不同的字节顺序:
- 大端序(Big Endian):IBM 系列、网络协议(比如 TCP/IP)用的是这个。
- 小端序(Little Endian):Intel 的 x86 架构用的是这个,影响最大。
🤔 那为什么不统一呢?
主要是因为:
1. 硬件取数效率不同
- Intel 的 CPU 内部是从低位到高位读数据,小端序更容易实现;
- Motorola 的 CPU 更倾向于从高位开始,大端序更自然。
每种架构都有自己的“优化路径”。
2. 人类阅读习惯 vs 机器处理习惯
- 大端序更符合人类习惯,我们读数字总是从高位开始(比如1234我们从1开始读);
- 小端序更方便机器处理,因为处理器常从低地址开始读写数据。
3. 网络标准用大端序
网络传输通常统一用大端序(叫网络字节序),为了让不同机器(大端的、还是小端的)交流时不出错,要在通信时显式转换。
✅ 总结:
| 原因 | 说明 |
|---|---|
| 历史遗留 | 不同芯片厂早期各自为政 |
| 性能优化 | 每种架构有不同的高效处理方式 |
| 人类习惯 | 大端更“自然” |
| 兼容通信 | 网络统一用大端,代码里常见字节序转换 |
如果你在写程序,尤其要处理文件格式、网络协议、跨平台数据时,就得考虑字节序了。日常开发(比如在 x86 上跑 Python/C++)基本用小端序。
2.1.2 void*
✅ void *p; 是合法的
这是定义了一个“指向任意类型的指针”。
在 C/C++ 中,void* 是一种通用指针类型,它可以指向任何类型的数据,但不能直接解引用,除非你先把它强制转换成其他类型的指针。
例如:
int x = 10;
void *p = &x; // OK,p 指向 int 类型的变量 x
❌ void a; 是错误的
这是因为 void 表示“无类型”或“没有返回值”,不能作为变量的类型。
换句话说:
int a;—— 定义一个整型变量,a占据4字节void a;—— 这就像是在说“我要一个‘没有类型’的变量”,这在 C 语言中是没有意义的,因为变量必须有明确的内存布局和类型。
void指针和常量总结
| 声明 | 含义 | 是否合法 |
|---|---|---|
void *p; |
定义一个通用指针,指向任意类型 | ✅合法 |
void a; |
定义一个没有类型的变量,没意义 | ❌非法 |
作业
(void *)ptr和(*(void **))ptr的结果是否相同? 其中ptr为同一个指针
没问题,我再简化一下解释。
假设 ptr 是一个普通的指针,例如:
int *ptr;
1. (void *)ptr
这行代码的作用是将 ptr 转换为 void * 类型。void * 是一种通用指针类型,它可以指向任何类型的数据。所以,(void *)ptr 只是简单地将 int * 指针转换成 void * 类型。
void *p = (void *)ptr;
这样,你得到的是一个指向 int 类型的指针 ptr,但是它被转换成了一个 void * 指针。
2. (*(void **))ptr
这行代码稍微复杂一些。它分为两步:
-
(void **)ptr:首先将
ptr(假设它是int *类型)强制转换成void **,即指向void *的指针。重要:这实际上是把
int *当作void **来处理,这会导致类型不匹配。ptr实际上是一个int *类型的指针,它指向的是一个int类型的数据,但在这里你告诉编译器:“ptr其实是指向void *的指针”,这会出问题。 -
*** 解引用**:然后你对
void **指针进行解引用,得到一个void *指针。也就是说,
*(void **)ptr试图从ptr指向的位置取出一个void *类型的值。
结果区别
- (void *)ptr:这只是一个简单的类型转换,它将
ptr变成void *指针。 - (*(void **))ptr:这会把
ptr当成指向void *的指针进行处理,然后解引用。
它们的行为会不同,因为第二个表达式涉及到⭐错误的类型转换(把 int * 误当作 void **),这会导致解引用的时候访问到错误的数据。
总结一下:
(void *)ptr直接将ptr转换为void *类型。(*(void **))ptr会先将ptr当作指向void *的指针处理,再解引用它。
2.1.3 特殊指针
野指针、空指针(NULL),void指针
2.1.4 作业
写出int,bool,float,指针变量与“零值”比较的if语句
int a = 0;
if(a==0) ;
bool a = false;
if(a) ;
int *p = NULL;
if(p == NULL)
float a = 0.0;
if(a>-0.000001 && a<0.000001)//⭐
2.2 指针计算
指针与指针相减:得到两个地址间的元素个数 (两个指针必须是同类型)

p2-p1 = (0x00001008 - 0x00001000)/sizeof(int) ==2
指针与整形数相加或减
1.指针加一个整形数n : 得到地址加nsizeof(指针类型)
2.指针减一个整形数n: 得到地址减nsizeof(指针类型)

2.2.1 作业-用ptr来初始化数组中的所有字符
出现 *** stack smashing detected ***: terminated(栈破坏检测)这种错误,是因为你的程序写越界了内存,破坏了系统堆栈,编译器或运行时系统检测到了这种情况,于是终止程序以防止更严重的问题发生(如漏洞利用)。
这一段代码:
for (int i = 0; i < 10; i++) {
ptr += i;
char c = random() % 26 + 97;
*ptr = c;
}
会导致 严重的越界访问,我们来一步步看发生了什么:
🔍 逐步分析 ptr += i 的效果:
假设 ptr 一开始指向 str[0],我们看每次循环后 ptr 的位置:
| i | 累积 ptr 偏移 | 写入地址 |
|---|---|---|
| 0 | ptr += 0 → 0 | str[0] |
| 1 | ptr += 1 → +1 | str[1] |
| 2 | ptr += 2 → +3 | str[3] |
| 3 | ptr += 3 → +6 | str[6] |
| 4 | ptr += 4 → +10 | ❌ str[10](越界) |
| 5 | ptr += 5 → +15 | ❌❌ 更严重越界 |
| ... | ... | ... |
你看,ptr 在每次循环都会 越来越快地跑远,到第 4 次循环时已经写到了 str[10],这已经越过 char str[10] 的末尾了,造成栈破坏(stack smashing)。
🚨 这类写法特别危险!
因为它不是简单的 +1,而是每次累加 i,属于“指数级炸锅”写法。最关键的是,你永远不能在指针累加中失控,否则写到了别人家后院了(别的局部变量,返回地址,运行时栈信息等),系统一看:“你在干嘛?破坏我栈结构?直接崩溃你吧”。
✅ 如何写才安全:
写法 1-指针移动方式:
ptr = str;
for (int i = 0; i < 10; i++) {
*ptr = random() % 26 + 97;
ptr++;
}
写法 2-不移动 ptr,用偏移:
for (int i = 0; i < 10; i++) {
*(ptr + i) = random() % 26 + 97;
}
🛡️ 最佳建议:
- 不要在指针上做复杂的累加运算,除非你100%掌握边界。
- 使用数组访问方式(
str[i])可读性和安全性更好。 - 加个结尾符
\0,打印字符串时才安全。 char str[11]会更稳妥。
2.2.2 作业-强制转换指针类型
//解引用写出下代码输出结果
int a[][2]={1,2,3,4};
printf("%d,", a[0][1]);
int *ptr = a[0];
printf("%d,", *(ptr+1));
char *p = (char*)a;
printf("%d\n", *(p+4));
答案:2,2,2
解析:注意char *p=(char*)a;
a原本是int*,被强制转换为char*,
所以p+4-->p+4个字节-->指向a[0][1]
2.2.3 练习
int a[2][3] = {1,2,3,4,5,6}
int *ptr = (int*)a;
char *p = (char*)a[0];
问题:
1. printf("%d\n", *((int*)a));
2. ptr+1指向哪个数
3. a[0]+1 指向哪个数
4. a+1指向哪里
5. &a+1指向哪里
6. printf("%x\n", *((int*)(p+1)))
让我们逐个分析你提出的问题,并给出详细解答。
1. printf("%d\n", *((int*)a));
首先,a 是一个 2x3 的二维数组,它在内存中的布局是连续的,按行主序排列(也就是说,所有元素按行放在内存中)。a 实际上是 int[2][3] 类型的数组,a 的首地址是 a[0],即第一行的首地址。
当你写 *((int*)a) 时,首先将 a 转换为 int*(指向整数的指针)。由于 a 实际上是一个指向二维数组的指针,(int*)a 会让指针指向数组的第一个整数。因为 a[0][0] 是数组的第一个元素,所以 *((int*)a) 相当于取 a[0][0] 的值,也就是 1。
因此,输出是:
1
2. ptr+1 指向哪个数?
ptr 是指向 a 的指针,但它已经被强制转换为 int* 类型。这意味着 ptr 实际上指向二维数组中的元素,而不是整个数组。在 ptr+1 处,指针偏移一个整数的大小(即 4 字节)。ptr指向a[0][0].
由于 a 是一个二维数组,ptr+1 指向 a[0][1],即数组中第二个元素,也就是 2。
因此,ptr+1 指向的是 a[0][1],也就是 2。
3. a[0]+1 指向哪个数?
a[0] 是数组的第一行,类型是 int[3]。a[0] 的类型是 int*,指向数组的第一个元素 a[0][0]。a[0]+1 相当于将指针偏移一个整数的大小,指向 a[0][1],即数组的第二个元素。
因此,a[0]+1 指向的是 a[0][1],也就是 2。
4. a+1 指向哪里?
a 是一个指向二维数组的指针,a+1 会将指针偏移一个 int[3] 大小。也就是说,它会跳过第一行,指向第二行的首地址,即 a[1]。
因此,a+1 指向的是 a[1],也就是数组的第二行 a[1][0] 的地址。
5. &a+1 指向哪里?
&a 是 a 数组的地址,也就是指向整个二维数组的指针。&a+1 相当于将指针偏移一个二维数组的大小,也就是偏移 2 * 3 * sizeof(int) 字节。它指向的是 a 后面紧跟着的内存位置,即二维数组之后的内存区域。
因此,&a+1 指向的是 a 后面的内存地址,但这个地址并不对应于任何有效的数组元素。
6. printf("%x\n", *((int*)(p+1)))
在小端序系统中,数据的存储顺序是低位字节存储在低地址,而高位字节存储在高地址。
代码分析:
int a[2][3] = {1,2,3,4,5,6};
int *ptr = (int*)a;
char *p = (char*)a[0];
a是一个二维数组,包含 2 行 3 列,总共有 6 个int元素。a[0]即a的第一行,地址是a[0]的首元素,即a[0][0]。ptr是指向int类型的指针,指向整个二维数组a(按int存储方式来看)。p是指向char类型的指针,指向a[0]的地址。这里a[0]是第一行的地址(即a[0][0]的地址),而char *p会以字节为单位访问内存。
printf("%x\n", *((int*)(p+1))) 的解释:
p是char *类型,它指向a[0]。a[0]是int类型的数组(int a[3]),即[1, 2, 3]。- 当你将
p转换为char *时,p按字节进行递增,指向的是a[0][0]的第一个字节。a[0][0]的值是1,在内存中表示为0x01 0x00 0x00 0x00(假设int是 4 字节且在小端序系统下存储)。 p+1实际上将p向后移动一个字节,这意味着p会指向a[0][0]的第二个字节,即0x00(小端顺序的第二个字节)。- 然后,
(int*)(p+1)将p+1强制转换为int *类型。这个转换是将p+1解释为一个int类型的地址。在小端序系统中,p+1指向的是a[0][0]的第二个字节(值是0x00),因此,(int*)(p+1)会从这个字节开始,读取一个 4 字节的int值。 - 从
p+1开始,系统会读取 4 字节内存内容,这 4 字节的值是 0x00 0x00 0x00 0x02(对应a[0][1]的值2)。在小端序中,这个int被存储为 0x02 0x00 0x00 0x00。 - 因此,
*((int*)(p+1))的值是0x2000000,即536870912,这是小端序系统中读取的值。
第6题结论:
由于小端序的内存表示,*((int*)(p+1)) 读取的是内存地址从 p+1 开始的 4 个字节,这些字节在小端序中解释为 0x2000000,因此输出的结果是 2000000。
总结:
*((int*)a)输出1ptr+1指向a[0][1],即2a[0]+1指向a[0][1],即2a+1指向a[1](即第二行)&a+1指向a后面紧跟的内存位置*((int*)(p+1))的输出为2000000
2.2.4 指针细节
数组中数组名称:数组首地址(类型与数组的元素类一致)
int a[2]={1,2}; a是数组名称, a的类型 int*
char b[]="hello" b是数组名,b的类型char*
int c[2][3]= {{1,2,3},{4,5,6}}
c是数组名称 c的类型int[3]* , c[0]数组名称,c[0]类型int*
int d=10; &d是d的地址,是int*
int a[2]; &a是数组a的地址 是int[2]*--有两个整形数的数组类型
int c[2][3];&c是数组c的地址,是int [2][3]
应用:
int a[2][3]= {1, 2, 3, 4, 5, 6}
int *p= (int*)(&a+1);
printf("%d”,*(p-1)) //6
int *q= (int*)(&a[0])+1)
printf("%d”,*(q-1)) //3
你提到的数组 int c[2][3] = {{1, 2, 3}, {4, 5, 6}}; 是一个 2 行 3 列的二维数组。其类型实际上是 int[2][3],这是一个包含 2 个元素的数组,每个元素又是一个包含 3 个 int 类型元素的数组。
为什么 c 的类型是 int[3]*?
c本身是一个二维数组,其类型是int[2][3]。但是,当你把c当作一个指针来使用时,实际上你是在指向第一行(即c[0]),而c[0]的类型是int[3],是一个包含 3 个int的数组。- 由于
c是一个二维数组,它的名字(即c)会被隐式转换成一个指向它的第一个元素的指针,即int(*)[3],这是指向一个包含 3 个int元素的数组的指针。
示例说明:
#include <stdio.h>
int main() {
int c[2][3] = {{1, 2, 3}, {4, 5, 6}};
// c 本身是 int[2][3]
// c[0] 是 int[3]
printf("%p\n", c);// 输出 c 的地址,类型是 int (*)[3]
printf("%p\n", &c[0]);//输出c[0]的地址,类型是 int (*)[3]
printf("%p\n", &c[0][0]);//输出c[0][0]的地址,类型是int *
return 0;
}
在这个代码中:
c是一个指向int[3]类型数组的指针,即int (*)[3]。c[0]是一个包含 3 个int元素的数组,因此它的类型是int[3]。&c[0]是一个指向int[3]数组的指针,类型是int (*)[3]。
总结:
c的类型是int (*)[3],它指向一个包含 3 个int元素的数组。c[0]是一个类型为int[3]的数组,它包含了 3 个int元素。
2.3 多级指针
例子:
int a=10;
int p = &a; //int型指针保存int类型变量的地址, 也可以说指向整形变量
int * (q) = &p
int ** (m) = &q
int ** (n) = &m;
通过指针n来访问a的值:***n

2.3.1 作业-三级指针
定义一个三级指针int ***ppp,通过它来管理一个二维整型数组a[3][4]。并初始化数组元素,最后输出数组元素(可以通过一级,二级指针转换)


#include<stdio.h>
int main(void)
{
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
int *p = *a;
int **pp = &p;
int ***ppp = &pp;
for(int i=0;i<12;i++)
printf("%d,%d\n",**a,ppp[0][0][i]);//*(**ppp+i)
}
3.数组类型指针
3.1 定义
1)指针数组:本身是一个数组,数组类型是指针(数组中的元素是指针)
int *a[3]a是一个数组有3个元素,每一个元素都是一个int类型指针
2)数组指针:本身是一个指针,指针类型是数组(指针指向一个数组)
int(*p)[3]p是一个指针,指向有3个整形数的数组
3)指针函数:本身是一个函数,函数返回值是一个指针
int *fun();fun是一个函数, 返回值是一个int类型指钱
4)函数指针:本身是一个指针,指针类型是一个函数类型(指针指向一类函数)
void(*fun)(void) ;fun是一个指针,指向一类(没有返回值没有参数的函数)
⭐例子:
#include <stdio.h>
int main(void)
{
int a[2][3]={1,2,3,4,5,6};
int(*ptr)[3]= a;//⭐数组指针
for(int i=0;i<2;i++)
{
for(int j=0;j<3;j++)
printf("%d”,*((*ptr)+j));//⭐等价于ptr[i][j]
ptr++;
}
}
练习:
int(*arr[3])[10]中 的arr是什么? -------- 数组指针数组
3.2 指针数组
3.3.1 练习-指针数组放入数组
定义三个字符数组char a[10], b[10], c[10];用户输入字符初始化这三个数组, 再把a, b, c存在name[3]数组中, 遍历输出name所有元素。
int main(void){
char a[10]={0},b[10]={0},c[10]={0};
scanf("%s", a);
scanf("%s", b);
scanf("%s", c);
char *name[3] = {a, b, c};
for(int i=0; i<3; i++){
printf("%s ", name[i])
}
}
3.3 函数指针数组
int (*e[7])(int, char);
这个声明:int (*e[7])(int, char); 在C语言中是一个比较典型的“看起来很复杂”的声明。我们可以逐步分析它的含义:
一步一步解析:
- 从标识符 e 开始:
e[7]:说明e是一个数组,有7个元素。(*e[7]):e数组里的每一个元素都是一个函数指针。⭐(*e[7])(int, char):这些函数指针指向的函数接受两个参数:一个int类型和一个char类型。int (*e[7])(int, char):这些函数返回int类型的值。⭐
总结:
int (*e[7])(int, char);
这行代码的意思是:
e是一个长度为 7 的数组,数组中的每个元素都是一个指针,指向一个接收int和char参数并返回int的函数。
举个例子,帮助理解:
#include <stdio.h>
int f1(int a, char b) {
printf("f1: %d %c\n", a, b);
return a;
}
int f2(int a, char b) {
printf("f2: %d %c\n", a, b);
return a + 1;
}
int main() {
int (*e[7])(int, char); // 声明数组
e[0] = f1;
e[1] = f2;
e[0](42, 'A'); // 调用 f1
e[1](100, 'B'); // 调用 f2
return 0;
}
你可以把 e 看作是“函数指针数组”,方便存储和调用多个函数。
3.4 练习
请指出如下语句的含义
void (p1(int))[10]; //p1是一个函数, 函数返回值指针,指针类型是数组10个指针类型, 数组中的元素是void*
float ((p2)(int,int,float))(int); //p2是一个指针, 指向一个函数, 函数有3个参数,和一个指针返回值, 指针类型是一个函数类型,函数有一个int参数和float返回值
typedef double (((*p3)())[10])(); //p3是一个类型, 函数指针类型, 函数没有参数, 返回值是一个有10个函数指针的数组类型
int ((p4())[10])(); //p4是一个函数, 函数没参数, 返回值是一个指向10个函数指针的数组类型, 函数没有参数, 有一个整形返回值
4.const
4.1 const修饰常量
修饰后,不能被修改
⭐例子:
#include <stdio.h>
//const int a=10;a并没独立分配空间其实是常量10
int main(void)
{
const int a= 10;//a是有自己的空间,a被const修饰为只读
int *p =(int*)&a;
*p = 123;
printf(“%d,%d\n",a,*p);
}
当 const 修饰一个 int 变量时,在全局作用域中,无法通过指针修改其值;
而在局部作用域中却可以通过指针修改。
4.1.1. 全局作用域的 const 变量:
首先,全局变量是存储在程序的全局数据区或只读数据区(例如 .rodata 或 .data 区)。当你声明一个 const 变量时,编译器会把它当作常量,并将它放置在一个特定的内存区域,这个区域通常是只读的。这意味着你不能修改它的值,即使你通过指针进行修改也无法成功。
-
内存分配和保护:编译器把
const变量当作只读数据,通常会对它进行特殊的保护。在这种情况下,程序运行时如果尝试修改这个变量(即使通过指针强制转换类型),系统会检测到这种非法的写操作并抛出异常或者导致程序崩溃。代码示例:
const int global_var = 10; // 全局常量 void change_value() { int* ptr = (int*)&global_var; // 强制转换 *ptr = 20; // 运行时错误 }在这里,尽管通过强制类型转换
(int*)&global_var让ptr成为一个int*,指向const int变量global_var,但是编译器仍然保护了这个常量。你不能通过指针修改它,程序会出错,或者崩溃。
4.1.2. 局部作用域的 const 变量:
在局部作用域中,const 变量的处理可能会稍微不同。局部变量通常存储在栈上(stack)。对于局部的 const 变量,编译器可能没有那么严格地保护它,并且栈上的变量(如果没有其他保护措施)可能不会像全局变量那样被放入只读区域。
-
栈变量和强制转换:局部变量通常不会被编译器强制保护成只读,尤其是当你进行强制类型转换时,编译器不会像全局常量那样自动防止修改。你可以通过指针来修改它,因为栈上的变量本身并没有像全局常量那样被放入只读区域。
代码示例:
void change_value() { const int local_var = 10; // 局部常量 int* ptr = (int*)&local_var; // 强制转换 *ptr = 20; // 这里会修改 local_var 的值 }在这段代码中,你通过强制类型转换将
const int*强行转换为int*,并修改了local_var的值。在这种情况下,程序可能不会报错,但是这是未定义行为,这意味着程序的行为是不确定的。尽管栈上的局部变量在某些情况下允许这种修改,但这种做法是不安全的,而且可能会导致一些不易察觉的错误或崩溃。
为什么全局和局部不同?
- 全局 const 变量:通常被存储在只读数据区,并受到严格的保护,不允许修改。
- 局部 const 变量:通常存储在栈上,编译器可能不强制保护它,因此在某些情况下(如强制类型转换)修改值是可能的,但这是不推荐的做法,并且会导致不稳定的行为。
总结:
- 在全局作用域,
const修饰的变量会被放入只读内存区域,不能通过指针修改,因为编译器会保护它,防止修改。 - 在局部作用域,
const修饰的变量通常不会受到同样的保护,强制转换指针后可能修改它的值,但这种做法是未定义行为,可能会导致不可预测的错误。
4.2 const修饰指针
int a=10;
1.const int *p=&a; //cosnt修饰的p ,p不能被赋值
2.int const *p = &a; //cosnt修饰的p ,p不能被赋值
*p=20;错误
p=&b;可以
3.int *const p = &a; //cosnt修饰的p ,p不能被赋值
p= &b;错误
*p=123;可以
4.const int *const p= &a; //cosnt修饰*p和p,p和p都不能被赋值
本文来自博客园,作者:cyt2730,转载请注明原文链接:https://www.cnblogs.com/cytwjyy/p/18853926

浙公网安备 33010602011771号