指针详解4
运行环境以Dev-C++、Visual Studio 2022、MacOS的命令行和Xcode为主
1.sizeof和strlen的对比
-
1.1
sizeof-
sizeof用于计算变量占据内存空间的大小,单位是字节。如果操作数是类型的话,计算的是使用该类型创建的变量所占内存空间的大小 -
sizeof只关注占用内存空间的大小,不在乎内存中存放什么数据
#include <stdio.h> int main(int argc, const char * argv[]) { int a = 0; printf("%zd\n", sizeof(a)); printf("%zd\n", sizeof a); printf("%zd\n", sizeof(int)); return 0; }![image]()
-
-
1.2
strlen-
strlen是C语言库函数,功能是求字符串长度,函数原型为size_t strlen(const char *str); -
计算从函数的参数
str指向的地址开始往后,直到\0之前的字符个数 -
strlen函数会一直向后找\0字符,直到找到为止,因此可能存在越界查找
#include <stdio.h> int main(int argc, const char * argv[]) { char arr1[3] = { 'a', 'b', 'c' }; char arr2[] = "abc"; printf("%zd\n", strlen(arr1)); printf("%zd\n", strlen(arr2)); printf("%zd\n", sizeof(arr1)); printf("%zd\n", sizeof(arr2)); int arr[] = { 1, 2, 3, 4, 5, 0}; printf("%zd\n", strlen(arr)); // 1 // 参考数组在内存中的分布,01即00000001,是1个字节,后面的0会被看成'\0' return 0; }![image]()
![image]()
sizeof strlen 1. sizeof是操作符1. strlen是库函数,使用前需要包含头文件string.h2. sizeof计算操作数所占内存的大小,单位是字节2. strlen用于求字符串长度,统计\0之前的字符个数3.不关注内存中存放什么数据 3.关注内存中是否有 \0,如果没有\0,就会持续向后找,可能会越界 -
2.数组和指针题目解析
-
2.1 数组名和意义
-
sizeof(数组名)中的数组名表示整个数组,计算的是整个数组的大小 -
&数组名中的数组名表示整个数组,取出的是整个数组的地址 -
除以上两种情况外,所有的数组名都表示首元素的地址
-
-
2.2 一维数组
#include <stdio.h>
int main(int argc, const char * argv[]) {
int a[] = {1, 2, 3, 4};
printf("%zd\n", sizeof(a)); // 16
// 数组名单独放在sizeof内部,a表示整个数组,计算整个数组占用内存的大小,即4B*4=16B
printf("%zd\n", sizeof(a + 0)); // 4或8
// a是数组名,未单独放在sizeof内部,表示数组首元素的地址,加上0依然是首元素的地址,计算该地址占用内存的大小
printf("%zd\n", sizeof(*a)); // 4 *(a + 0) = a[0]
// a表示数组首元素的地址,解引用后取到首元素,int型长度为4B
printf("%zd\n", sizeof(a + 1)); // 4或8
// a表示数组首元素的地址,a+1表示第2个元素的地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(a[1])); // 4 计算第2个元素的长度
printf("%zd\n", sizeof(&a)); // 4或8
// a与&放一块,这里的a表示整个数组,&a就是整个数组的地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(*&a)); // 16
// 思路1:*&a的*和&抵消,就是sizeof(a)
// 思路2:&a是数组的地址,类型为int (*)[4],*&a就是访问这个数组
printf("%zd\n", sizeof(&a + 1)); // 4或8
// &a是数组的地址,&a+1是跳过整个数组后新位置的地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(&a[0])); // 4或8
// 第1个元素的地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(&a[0] + 1)); // 4或8
// 第2个元素的地址,地址占据内存长度为4B或8B
return 0;
}

- 2.3 字符数组
// 代码1
#include <stdio.h>
int main(int argc, const char * argv[]) {
char arr[] = { 'a','b','c','d','e','f' };
printf("%zd\n", sizeof(arr)); // 6
// arr单独放在sizeof内部,表示整个数组,占据内存6B
printf("%zd\n", sizeof(arr + 0)); // 4或8
// arr表示数组首元素的地址,加0依然是首元素地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(*arr)); // 1
// arr未单独放在sizeof内部,它表示数组首元素的地址,解引用取到了首元素,char型占据内存1B
printf("%zd\n", sizeof(arr[1])); // 1
// 计算字符'b'占据内存的长度
printf("%zd\n", sizeof(&arr)); // 4或8
// &arr表示整个数组的地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(&arr + 1)); // 4或8
// &arr表示整个数组的地址,&arr+1表示跳过整个数组后新位置的地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(&arr[0] + 1)); // 4或8
// &arr[0] + 1即&arr[1],地址占据内存长度为4B或8B
return 0;
}

// 代码2
#include <stdio.h>
int main(int argc, const char * argv[]) {
char arr[] = { 'a','b','c','d','e','f' };
printf("%zd\n", strlen(arr)); // 随机值,字符数组中没有 '\0'
printf("%zd\n", strlen(arr + 0)); //随机值,同上
// printf("%zd\n", strlen(*arr)); // 程序崩溃,无法运行
// arr是数组名表示首元素的地址,*arr即首元素'a',其值为97
// 强行将97传递给strlen,函数会将97认做地址,然后去访问内存,但这是非法访问,程序奔溃
printf("%zd\n", strlen(arr[1])); // 同上,程序崩溃,无法运行
// 强行将字符'b'的值98传递给strlen,非法访问
printf("%zd\n", strlen(&arr)); // 随机值
// &arr 表示整个数组的地址,在数值上和数组首元素地址相等,由此向后找'\0',但字符数组中没有 '\0',因此是随机值
printf("%zd\n", strlen(&arr + 1)); //随机值
// &arr表示整个数组的地址,&arr+1表示跳过整个数组后新位置的地址,从它开始向后找'\0'
printf("%zd\n", strlen(&arr[0] + 1)); //随机值
// &arr[0] + 1即&arr[1],从第2个数组元素开始往后找'\0',但字符数组中没有 '\0',因此是随机值
return 0;
}

// 代码3
#include <stdio.h>
int main(int argc, const char * argv[]) {
char arr[] = "abcdef"; // 字符串末尾存在'\0'
printf("%zd\n", sizeof(arr)); // 7
// arr单独放在sizeof内部表示整个数组,占据内存7B
printf("%zd\n", sizeof(arr + 0)); // 4或8
// arr未单独放在sizeof内部,它表示数组首元素的地址,加0依然是首元素地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(*arr)); // 1
// 思路1:arr是首元素的地址,解引用后取到首元素,占据内存1B
// 思路2:*arr --> *(arr + 0) --> arr[0]
printf("%zd\n", sizeof(arr[1])); // 1 下标为1的字符,占据内存1B
printf("%zd\n", sizeof(&arr)); // 4或8
// &arr表示整个数组的地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(&arr + 1)); // 4或8
// &arr表示整个数组的地址,&arr+1表示跳过整个数组后新位置的地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(&arr[0] + 1)); // 4或8
// 即&arr[1],是一个地址,占据内存长度为4B或8B
return 0;
}

// 代码4
#include <stdio.h>
int main(int argc, const char * argv[]) {
char arr[] = "abcdef"; // 字符串末尾存在'\0'
printf("%zd\n", strlen(arr)); // 6
// arr表示首元素的地址,从字符'a'开始往后统计'\0'之前的字符个数,共6个
printf("%zd\n", strlen(arr + 0)); // 6
// arr与arr+0均表示首元素的地址,从字符'a'开始往后统计'\0'之前的字符个数,共6个
printf("%zd\n", strlen(*arr)); // 程序崩溃
// *arr --> *(arr+0) --> 'a' --> 97, 97作为地址传给给了strlen,但该地址不能访问
printf("%zd\n", strlen(arr[1])); // 程序崩溃,理由同上
printf("%zd\n", strlen(&arr)); // 6
// &arr表示整个数组的地址,与数组首元素的地址指向同一个位置,从字符'a'开始往后统计'\0'之前的字符个数,共6个
printf("%zd\n", strlen(&arr + 1)); // 随机值
// &arr表示整个数组的地址,&arr+1表示跳过整个数组后新位置的地址,此处存储内容未知,不知道'\0'在何处,因此结果不确定
printf("%zd\n", strlen(&arr[0] + 1)); // 5
// &arr[0] + 1 即 &arr[1],从字符'b'开始往后统计'\0'之前的字符个数,共5个
return 0;
}

// 代码5
#include <stdio.h>
int main(int argc, const char * argv[]) {
char *p = "abcdef"; // p指向字符串字面量的第1个字符
printf("%zd\n", sizeof(p)); // 4或8
// p是指针变量,即地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(p + 1)); // 4或8
// p是指针变量,p+1指向'b',地址占据内存长度为4B或8B
printf("%zd\n", sizeof(*p)); // 1
// *p --> *(p+0) --> p[0] --> 'a',字符型变量占据内存1B
printf("%zd\n", sizeof(p[0])); // 1 同上
printf("%zd\n", sizeof(&p)); // 4或8
// p是指针变量,&p是指针变量的地址,即地址的地址,也是二级指针char **,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(&p + 1)); // 4或8
// &p+1也是地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(&p[0] + 1)); // 4或8
// &p[0] + 1即p+1,地址占据内存长度为4B或8B
return 0;
}

// 代码6
#include <stdio.h>
int main(int argc, const char * argv[]) {
char* p = "abcdef"; // p指向字符串字面量的第1个字符
printf("%zd\n", strlen(p)); // 6
// 从'a'开始计算'\0'之前的字符个数
printf("%zd\n", strlen(p + 1)); // 5
// 从'b'开始计算'\0'之前的字符个数
printf("%zd\n", strlen(*p)); // 程序崩溃
// *p --> *(p+0) --> p[0] --> 'a' --> 97 将97作为地址传给strlen,非法访问
//printf("%zd\n", strlen(p[0])); // 程序崩溃,同上
printf("%zd\n", strlen(&p)); // 随机值
// &p是指针变量的地址,即地址的地址,此处存储内容和'\0'的位置均未知
printf("%zd\n", strlen(&p + 1)); // 随机值,同上
printf("%zd\n", strlen(&p[0] + 1)); // 5
// &p[0] + 1即p + 1,从'b'开始计算'\0'之前的字符个数
return 0;
}

- 2.4 二维数组
#include <stdio.h>
int main(int argc, const char * argv[]) {
int a[3][4] = { 0 };
printf("%zd\n", sizeof(a)); // 48
// a单独放在sizeof内部表示整个数组,求整个数组占据内存的大小,4B*12=48B
printf("%zd\n", sizeof(a[0][0])); // 4
// a[0][0]即第1行第1列元素0,整型占据内存4B
printf("%zd\n", sizeof(a[0])); // 16
// a[0]是第1行的数组名,单独放在sizeof内部表示整个第1行,共4B*4=16B
printf("%zd\n", sizeof(a[0] + 1)); // 4或8
// a[0]是第1行的数组名,未单独放在sizeof内部,表示&a[0][0],加1后即&a[0][1],地址占据内存长度为4B或8B
printf("%zd\n", sizeof(*(a[0] + 1))); // 4
// a[0] + 1即&a[0][1],解引用后为a[0][1],整型占据内存4B
printf("%zd\n", sizeof(a + 1)); // 4或8
// a是数组名,未单独放在sizeof内部,表示首元素的地址,即第1行的地址。加1后表示第2行的地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(*(a + 1))); // 16
// a+1即第2行的地址,解引用后取到了第2行,4B*4=16B
printf("%zd\n", sizeof(&a[0] + 1)); // 4或8 第2行的地址
// a[0]是第1行的数组名,&a[0]表示第1行数组的地址,加1后是第2行的地址,地址占据内存长度为4B或8B
printf("%zd\n", sizeof(*(&a[0] + 1))); // 16
// &a[0] + 1即第2行的地址,解引用后取到了第2行,4B*4=16B
printf("%zd\n", sizeof(*a)); // 16
// a是数组名,未单独放在sizeof内部,表示数组首元素的地址,即第1行的地址。解引用后取到了第1行,4B*4=16B
printf("%zd\n", sizeof(a[3])); // 16
// a[3]即行标为3的整行的地址,越界,但sizeof内部的表达式不会真实计算,4B*4=16B
return 0;
}

3.指针运算题目解析
- 3.1 分析以下程序的运行结果
// &a + 1的类型是数组指针int (*)[5],代码将其强制转换为整形指针int *
#include <stdio.h>
int main(int argc, const char * argv[]) {
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1)); // 打印结果:2,5
return 0;
}

- 3.2 在x86环境下,假设结构体的大小是20B,分析以下程序的输出结果
#include <stdio.h>
struct Test {
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
} * p = (struct Test*)0x100000;
// 将0x100000地址强制转为结构体指针变量p
int main(int argc, const char * argv[]) {
// p指向了结构体,跳过1个单位即20B的结构体空间,以十六进制表示20为14
// x86环境意为32bit的地址总线,0x00100000 + 0x00000014 = 0x00100014
printf("%p\n", p + 0x1);
// 将p强制转为无符号长整型,这样0x100000不再表示地址,而是普通整数
// 整数加1即数值加1,0x0010000 + 0x00000001 = 0x00100001
printf("%p\n", (unsigned long)p + 0x1);
// 将p从结构体指针强制转换为无符号整型指针,0x100000依然是地址
// 整型指针加1即跳过4B,0x00100000 + 0x00000004 = 0x00100004
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}

- 3.3 分析以下程序的运行结果
#include <stdio.h>
int main(int argc, const char * argv[]) {
// 数组元素是逗号表达式,先简化为 {1, 3, 5, 0, 0, 0}
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p = NULL;
// a[0]是第1行数组的数组名,指向一维数组的第1个元素
p = a[0];
printf("%d", p[0]); // 打印结果:1
return 0;
}

- 3.4 在x86环境下,分析以下程序的输出结果
#include <stdio.h>
int main(int argc, const char * argv[]) {
int a[5][5] = {0};
int (*p)[4] = {NULL};
// a是二维数组的数组名,表示数组首元素的地址,即第1行的地址,是数组指针类型 int (*)[5]
p = a; // 两者类型不一致,会有警告
// 由指向图可知两数组元素存储位置间隔4个,且后方地址更大,以十进制数打印时结果为-4
// 以地址打印时,会打印-4的补码,地址没有负数,按照源码、反码和补码的运算规则得到0xfffffffc
// -4 10000000 00000000 00000000 00000100
// 11111111 11111111 11111111 11111100
// ff ff ff fc
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}


- 3.5 分析以下程序的运行结果
#include <stdio.h>
int main(int argc, const char * argv[]) {
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// &aa表示整个数组的地址,加1后指向了整个数组末尾,并强制转为整型指针
int* ptr1 = (int*)(&aa + 1);
// aa表示数组首元素(第1行)的地址,加1后为第2行的地址,解引用后取到了第2行,隐式转换成第2行首元素的地址
int* ptr2 = (int*)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1)); // 打印结果:10,5
return 0;
}

- 3.6 分析以下程序的执行结果
#include <stdio.h>
int main(int argc, const char * argv[]) {
// a是字符指针数组,其每个元素是字符指针(地址),它各指向了3个字符串
char* a[] = { "work", "at", "alibaba" };
// pa是二级指针,它指向了字符数组a中第一个元素,即work中'w'的首地址
char** pa = a;
// pa原来的指向失效,重新指向了at中'a'的首地址
pa++;
printf("%s\n", *pa); // 打印结果:at
return 0;
}
// 图中的地址均为假设


- 3.7 分析以下程序的执行结果
#include <stdio.h>
int main(int argc, const char * argv[]) {
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
// cpp存储的是cp的地址,它指向了cp,也指向了cp[0]。++cpp后新的cpp指向了cp[1]
// 第1次解引用取到了cp[1]的值,即0xcfe2,这是一个地址,指向了c[2]
// 第2次解引用取到了c[2]的值,即0x100B,这也是一个地址,指向了POINT,打印一直到'\0'之前的字符
printf("%s\n", **++cpp); //打印结果:POINT
// 上文分析cpp指向了cp[1],继续++cpp后新的cpp指向了cp[2]
// 第1次解引用取到了cp[2]的值,即0xcfde,这是一个地址,指向了c[1]
// c[1]的地址自减之后指向c[0],即0xcfda,也是一个地址,指向了c[0]
// 第2次解应用取到了c[0]的值,即0x1001,这个地址指向了ENTER
// 最后加3,地址变为0x1004,指向了字符串"ENTER"的字符'E',打印一直到'\0'之前的字符
printf("%s\n", *-- * ++cpp + 3); // ER
// *cpp[-2] + 3即*(*(cpp - 2)) + 3,上文分析cpp指向了cp[2],cpp-2指向了cp[0],即0x31f2
// 第1次解引用取到了0xcfe6,这是一个地址,指向了c[3]
// 第2次解引用后取到了0x1011,也是一个地址,指向了FIRST
// 最后加3,地址变为0x1014,指向了字符串"FIRST"的字符'S',打印一直到'\0'之前的字符
printf("%s\n", *cpp[-2] + 3); // ST
// cpp[-1][-1] + 1即*(*(cpp - 1) - 1) + 1,上文分析cpp指向了cp[2],cpp-1指向了cp[1],即0x31f6
// 第1次解引用取到了0xcfe2,这是一个地址,指向了c[2],c[2]的地址再减1是c[1]的地址0xcfde
// 第1次解引用取到了c[1]的值0x1007,也是一个地址,指向了NEW
// 最后加1,地址变为0x1008,指向了字符串"NEW"的字符'E',打印一直到'\0'之前的字符
printf("%s\n", cpp[-1][-1] + 1); // EW
return 0;
}
// 图中的地址均为假设






浙公网安备 33010602011771号