数组-指针

总结:

定义:

数据类型 数组名称[长度];

//数组特性,空间连续,同类型数据, 数组名是数组首地址,第一个元素地址,常量,下标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
};
image

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

这行代码稍微复杂一些。它分为两步:

  1. (void **)ptr:首先将 ptr(假设它是 int * 类型)强制转换成 void **,即指向 void * 的指针。

    重要:这实际上是把 int * 当作 void ** 来处理,这会导致类型不匹配。ptr 实际上是一个 int * 类型的指针,它指向的是一个 int 类型的数据,但在这里你告诉编译器:“ptr 其实是指向 void * 的指针”,这会出问题。

  2. *** 解引用**:然后你对 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 指针计算

指针与指针相减:得到两个地址间的元素个数 (两个指针必须是同类型)
image

p2-p1 = (0x00001008 - 0x00001000)/sizeof(int) ==2

指针与整形数相加或减
1.指针加一个整形数n : 得到地址加nsizeof(指针类型)
2.指针减一个整形数n: 得到地址减n
sizeof(指针类型)
image

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;
}

🛡️ 最佳建议:
  1. 不要在指针上做复杂的累加运算,除非你100%掌握边界。
  2. 使用数组访问方式(str[i])可读性和安全性更好。
  3. 加个结尾符 \0,打印字符串时才安全。
  4. 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))) 的解释:

  1. p 是 char * 类型,它指向 a[0]。a[0] 是 int 类型的数组(int a[3]),即 [1, 2, 3]。
  2. 当你将 p 转换为 char * 时,p 按字节进行递增,指向的是 a[0][0] 的第一个字节。a[0][0] 的值是 1,在内存中表示为 0x01 0x00 0x00 0x00(假设 int 是 4 字节且在小端序系统下存储)。
  3. p+1 实际上将 p 向后移动一个字节,这意味着 p 会指向 a[0][0] 的第二个字节,即 0x00(小端顺序的第二个字节)。
  4. 然后,(int*)(p+1) 将 p+1 强制转换为 int * 类型。这个转换是将 p+1 解释为一个 int 类型的地址。在小端序系统中,p+1 指向的是 a[0][0] 的第二个字节(值是 0x00),因此,(int*)(p+1) 会从这个字节开始,读取一个 4 字节的 int 值。
  5. 从 p+1 开始,系统会读取 4 字节内存内容,这 4 字节的值是 0x00 0x00 0x00 0x02(对应 a[0][1] 的值 2)。在小端序中,这个 int 被存储为 0x02 0x00 0x00 0x00。
  6. 因此,*((int*)(p+1)) 的值是 0x2000000,即 536870912,这是小端序系统中读取的值。

第6题结论:

由于小端序的内存表示,*((int*)(p+1)) 读取的是内存地址从 p+1 开始的 4 个字节,这些字节在小端序中解释为 0x2000000,因此输出的结果是 2000000。


总结:

  • *((int*)a) 输出 1
  • ptr+1 指向 a[0][1],即 2
  • a[0]+1 指向 a[0][1],即 2
  • a+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
image

2.3.1 作业-三级指针

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

image

#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语言中是一个比较典型的“看起来很复杂”的声明。我们可以逐步分析它的含义:


一步一步解析:

  1. 从标识符 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都不能被赋值

posted @ 2025-04-29 16:20  cyt2730  阅读(100)  评论(0)    收藏  举报