指针常见误区总结

目录

指针的加减运算

合法、非法指针运算汇总

指针和数组的核心混淆点

核心等价公式

字符数组与字符指针

char str1[] = "hello"; 字符数组

 char *str2 = "hello"; 字符指针


指针的加减运算

普通整数加减:x + 1,数值直接 + 1。 指针加法不是!

c

int *p;
p = p + 1;

并不是地址值 + 1 字节。 

真实规则: 新地址 = 原地址 + 1 × sizeof(*p)

int占 4 字节:p+1,内存地址实际向后偏移4 字节,跳到下一个 int 元素。 char*占 1 字节:p+1地址只 + 1 字节。 double*占 8 字节:p+1地址向后偏移 8 字节。

重点:指针的步长,完全由它指向的数据类型决定,不是指针本身大小。

示例:

c

int arr[] = {0x1111,0x2222,0x3333};
int *p = arr;

假设数组首地址:0x1000

  • p0x1000,指向第一个元素
  • p+10x1004,指向第二个元素
  • p+20x1008,指向第三个元素

合法、非法指针运算汇总

✅允许

  1. 指针 ± 整数(向前 / 向后移动若干个元素)
  2. 两个同类型指针相减:得到中间相隔元素个数:p2 - p1,结果 = (地址差) / sizeof(*p)

❌绝对不允许

  1. 指针 + 指针:没有意义,编译器报错
  2. 指针 * / 指针:无意义

c

#include <cstdio>
int main(void)
{
    int arr[] = {10,20,30};
    int *p = arr;

    printf("p   =%p\n", p);
    printf("p+1 =%p\n", p+1);
    printf("p+2 =%p\n", p+2);

    char *cp = (char*)arr;
    printf("cp   =%p\n", cp);
    printf("cp+1 =%p\n", cp+1);
    return 0;
}

观察:int 指针 + 1 地址跳 4;char 指针 + 1 地址只跳 1。


思考

  1. int* pp+3,地址实际增加多少字节?
  2. 两个同类型指针相减,得到的是字节差吗?
  3. 指针可以做指针 + 指针运算吗?

1.12

2.不是

3.不可以

✅批改:

  1. int 占 4 字节,p+3:\(3×4=\boldsymbol{12}\)字节 ✔
  2. 不是字节差,是元素个数差(地址差 ÷ sizeof (*p))✔
  3. 指针 + 指针属于非法运算 ✔

指针相减,只限定两个指针指向同一块连续数组内存才有意义。如果两个指针指向完全无关两块内存,相减结果无定义。


指针和数组的核心混淆点

很多人以为数组和指针完全一样,并不是,只是语法可以互相替代,内存本质完全不同

c

int arr[5] = {1,2,3,4,5};
int *p = arr;

arr 数组名,在大多数表达式里,会隐式转换为首元素的地址

所以 int *p = arr;

等价于 int *p = &arr[0];

核心等价公式

arr[i]    ≡    ∗(arr+i)    ≡    ∗(i+arr)    ≡p[i]

arr[2] 完全等价 *(arr+2)

所以 C 语言下标语法本质就是指针运算。

⚠️数组名不是指针变量

  1. 数组 arr:是一块连续内存的别名,本身不是变量,不能修改 arr = arr + 1; ❌ 编译报错!数组名不能被赋值、不能自增 arr++。 数组大小编译期确定:sizeof(arr) 得到整个数组总字节。

  2. 指针 p:是独立变量,存地址,可以修改 p = p +1; ✅合法,可以改变 p 存的地址; sizeof(p):64 位环境永远等于 8 字节,和数组多大没关系

表格对比

表格

表达式int arr[5]数组int *p = arr指针
sizeof()20(5×4,整个数组大小)8(64 位指针本身大小)
能否 arr++ / p++❌ arr++ 报错✅ p++ 允许
存储内容直接存 5 个 int 数据存放数组首元素地址

现象↓:

c

#include <cstdio>
int main(void)
{
    int arr[5] = {10,20,30,40,50};
    int *p = arr;

    printf("arr[2] = %d\n", arr[2]);
    printf("*(arr+2) = %d\n", *(arr+2));
    printf("p[2] = %d\n", p[2]);

    printf("sizeof(arr)=%zu\n", sizeof(arr));
    printf("sizeof(p)=%zu\n", sizeof(p));

    // arr = arr +1; //打开这行直接编译报错
    p = p +1;
    printf("p移动后 p[0]=%d\n", p[0]);
    return 0;
}

运行观察:sizeof(arr)输出 20;sizeof(p)输出 8;arr++语法报错。

函数参数 void func(int arr[]),函数内部的arr不是数组!退化成指针,此时sizeof(arr)得到指针大小,得不到数组长度。


思考

  1. arr[3] 使用指针写法等价写成什么?
  2. sizeof(arr)sizeof(p) 结果为什么不一样?
  3. arr++ 是否可以编译通过?

1.*(arr + 3)

2.一个是整个数组的大小 一个是指针的大小

3.不可以

  1. arr[3]等价*(arr+3),甚至*(3+arr)语法同样合法。
  2. sizeof(arr)求整个数组占用字节;sizeof(p)指针变量本身大小,64 位固定 8 字节,和指向数组大小无关。
  3. arr++❌编译报错:数组名不是变量,不能被修改。

🔴 当数组作为函数形参void test(int arr[])

这里arr会被编译器直接退化为int* arr。 函数内部sizeof(arr)得到指针大小,

拿不到数组真实长度。


字符数组与字符指针

同样写"hello",两种写法内存分布天差地别。

c

char str1[] = "hello";
char *str2 = "hello";

char str1[] = "hello"; 字符数组

  • "hello"拷贝到栈内存数组里面。
  • 内存:h e l l o \0,一共 6 字节。
  • ✅内存可读写,可以修改字符:str1[0]='X';合法。
  • sizeof(str1)得到 6,数组总字节。

 char *str2 = "hello"; 字符指针

  • "hello"字符串字面量(常量),存放在只读常量段,不在栈。
  • str2只是一个指针变量(64 位占 8 字节),仅仅保存字符串常量的首地址
  • 禁止修改str2[0]='X';,未定义行为,程序直接崩溃!不能写只读内存。
  • sizeof(str2)得到 8,指针本身大小,和字符串长度无关。

内存对比表格

表格

写法内存位置能否修改内容sizeof 结果
char str1[]="hello"栈空间✅可修改6
char *str2="hello"只读常量段❌禁止修改8

补充小现象:

c

char *a = "abcd";
char *b = "abcd";

编译器会优化,ab指向同一块只读常量地址,节约内存。

实操代码,可以 VS 运行:

c

#include <cstdio>
int main(void)
{
    char str1[] = "hello";
    char *str2 = "hello";

    str1[0] = 'M';
    printf("%s\n", str1);

    // str2[0] = 'M'; //打开这行,程序运行直接崩溃!
    printf("sizeof(str1)=%zu\n", sizeof(str1));
    printf("sizeof(str2)=%zu\n", sizeof(str2));
    return 0;
}

函数传参,如果返回局部字符数组的地址,数组函数结束栈销毁,返回野指针; 但是返回字符串字面量return "ok";是安全的,因为常量区生命周期是整个程序。


思考

  1. char str[] = "test"sizeof(str)等于多少?
  2. char *s = "test";,能否执行s[1]='A'
  3. 返回局部函数内char buf[]数组的地址,会产生什么问题?

1.5

2.不能

3.出现野指针

  1. char str[] = "test" 字符串末尾自带\0结束符,实际内存:t e s t \0,一共 5 字节。你的答案数值是对。

  2. ✔不能。s指向只读字符串常量区,s[1]='A'运行崩溃,未定义行为。

  3. ✔函数内部局部char buf[]属于栈上局部变量。函数执行结束,栈内存释放销毁。返回它的地址,得到野指针。后续访问这块内存,内容已经乱掉。

返回局部数组地址是野指针问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值