8

揭秘C语言的心脏:深入探索指针与数组的奥秘 - Betty’sSweet

 7 months ago
source link: https://www.cnblogs.com/bett/p/18001744
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
pFp8UCq.jpg

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C语言学习
贝蒂的主页:Betty‘s blog


3371071-20240201173653318-942043696.png

1. strlen()和sizeof的区别

名称 区别
sizeof 1. sizeof是操作符
2. sizeof计算操作数所占内存的⼤⼩,单位是字节
3. 不关注内存中存放什么数据
strlen 1. strlen是库函数,使⽤需要包含头⽂件 string.h
2. srtlen是求字符串⻓度的,统计的是 '\0' 之前字符的隔个数
3. 关注内存中是否有'\0' ,如果没有'\0',就会持续往后找,可能会越界

2. 数组名的理解

  1. sizeof(数组名),数组名单独放在括号里,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。
  2. &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表⽰⾸元素的地址。

3. 一维数组

3.1 题目

int main()
{
	//输出结果?
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a + 0));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(a[1]));
	printf("%d\n", sizeof(&a));
	printf("%d\n", sizeof(*&a));
	printf("%d\n", sizeof(&a + 1));
	printf("%d\n", sizeof(&a[0]));
	printf("%d\n", sizeof(&a[0] + 1));
	return  0;
}

3.2 输出结果

3371071-20240201173653557-610582415.png
  • 该环境为VS2022,×64环境。后续也会在该环境下实验

3.3 解析

  1. sizeof(a),这⾥的a是数组名表⽰整个数组,计算整个数组的大小,数组每个元素是整型,一共有四个元素。所以4*4=16
  2. a+0是个表达式,这时a就是数组首元素地址,a+0仍是首元素地址,地址在×86环境下大小为4,在×64环境下大小为8
  3. a是数组首元素地址,对a解引用得到1,1是整型,所以大小为4
  4. a是数组首元素地址,a+1是第二个元素的地址,在×86环境下大小为4,在×64环境下大小为8
  5. a[1]是数组第二个元素2,是一个整型,大小为4
  6. &a是对数组名取地址,代表整个数组的地址,在×86环境下大小为4,在×64环境下大小为8
  7. &a的&和*相互抵消,相当于sizof(a),也就是第一步,大小为16
  8. &a+1指的是以整个数组的地址为单位,跳过整个数组之后的地址,仍是地址,在×86环境下大小为4,在×64环境下大小为8
  9. &a[0]就是数组首元素的地址,在×86环境下大小为4,在×64环境下大小为8
  10. &a[0]+1就是第二个元素地址,与a+1等价,在×86环境下大小为4,在×64环境下大小为8

4. 字符数组

4.1 题目一

int main()
{
	//输出结果?
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));
	return 0;
}

(1) 输出结果

3371071-20240201173653753-1153326856.png

(2) 解析

  1. sizeof(arr),这⾥的arr是数组名表⽰整个数组,计算整个数组的大小,数组每个元素是字符型,一共6个元素,1*6=6
  2. a+0是个表达式,这时a就是数组首元素地址,a+0仍是首元素地址,地址在×86环境下大小为4,在×64环境下大小为8
  3. arr是数组首元素地址,对arr解引用得到a,a是字符型,所以大小为1
  4. arr[1]是数组第二个元素b,大小为1
  5. &arr是对数组名取地址,代表整个数组的地址,在×86环境下大小为4,在×64环境下大小为8
  6. &arr+1指的是以整个数组的地址为单位,跳过整个数组之后的地址,仍是地址,在×86环境下大小为4,在×64环境下大小为8
  7. &arr[0]+1就是第二个元素地址,与arr+1等价,在×86环境下大小为4,在×64环境下大小为8

4.2 题目二

int main()
{
	//输出结果?
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
         printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	return 0;
}

(1) 输出结果

3371071-20240201173653943-951439065.png

(2) 解析

  1. strlen是以'\0'为标志的,如果数组里没有,会继续往内存中寻找,直到找到'\0',所以是个随机值
  2. arr+0也是首元素地址,与1同理,所以也是随机值
  3. &arr是对数组名取地址,代表整个数组的地址,为了存储方便也会以数组首元素的地址表示,所以和1.2值相同,也是个随机值
  4. &arr+1指的是以整个数组的地址为单位,跳过整个数组之后的地址,不知道'\0'在哪,所以也是随机值
  5. &arr[0]+1就是第二个元素地址,一直找到内存中的\0',是个随机值且会比1的长度少一
  6. strlen的参数要传地址进去,否则就会出错
  7. strlen的参数要传地址进去,否则就会出错

4.3 题目三

int main()
{
	//输出结果?
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));
	return 0;
}

(1) 输出结果

3371071-20240201173654142-1922070257.png

(2) 解析

  1. sizeof(arr),计算整个数组的大小,字符串默认的结束标志为'\0',所以数组一共有7个元素,每个元素都是字符型,1*7=7
  2. arr+0是个表达式,这时arr数组首元素地址,arr+0仍是首元素地址,地址在×86环境下大小为4,在×64环境下大小为8
  3. arr是数组首元素的地址,对其解引用等到第一个元素a,a为字符型,大小为1
  4. arr[1]是数组第二个元素b,大小也为1
  5. &arr是对数组名取地址,代表整个数组的地址,在×86环境下大小为4,在×64环境下大小为8
  6. &arr+1指的是以整个数组的地址为单位,跳过整个数组之后的地址,仍是地址,在×86环境下大小为4,在×64环境下大小为8
  7. &arr[0]+1就是第二个元素地址,与arr+1等价,在×86环境下大小为4,在×64环境下大小为8

4.4 题目四

int main()
{
	//输出结果?
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	return 0;
}

(1) 输出结果

3371071-20240201173654318-74617115.png

(2) 解析

  1. strlen是以'\0'为标志的,字符串默认结束标志位'\0',所以是6
  2. arr+0也是首元素地址,与1同理,所以也是6
  3. &arr是对数组名取地址,代表整个数组的地址,为了存储方便也会以数组首元素的地址表示,所以和1.2值相同,也是6
  4. &arr+1指的是以整个数组的地址为单位,跳过整个数组之后的地址,不知道'\0'在哪,所以是随机值
  5. &arr[0]+1就是第二个元素地址,一直找到内存中的\0',会比1的长度少一,是5
  6. strlen的参数要传地址进去,否则就会出错
  7. strlen的参数要传地址进去,否则就会出错

4.5 题目五

int main()
{
	//输出结果?
	char* p = "abcdef";
	printf("%d\n", sizeof(p));
	printf("%d\n", sizeof(p + 1));
	printf("%d\n", sizeof(&p));
	printf("%d\n", sizeof(&p + 1));
	printf("%d\n", sizeof(&p[0] + 1));
	printf("%d\n", sizeof(*p));
	printf("%d\n", sizeof(p[0]));
	return 0;
}

(1) 输出结果

3371071-20240201173654521-1766357745.png

(2) 解析

  1. 字符串存储的是首元素地址,所以p相当于首元素地址,在×86环境下大小为4,在×64环境下大小为8
  2. 同理p+1是第二个元素的地址,在×86环境下大小为4,在×64环境下大小为8
  3. 对p取地址,相当于得到还是地址,在×86环境下大小为4,在×64环境下大小为8
  4. &arr+1指的是以整个数组的地址为单位,跳过整个数组之后的地址,仍是地址,在×86环境下大小为4,在×64环境下大小为8
  5. &p[0]+1就是第二个元素地址,在×86环境下大小为4,在×64环境下大小为8
  6. *p与p[0]都是第一个元素,大小为1

4.6 题目六

int main()
{
	//输出结果?
	char* p = "abcdef";
	printf("%d\n", strlen(p));
	printf("%d\n", strlen(p + 1));
	printf("%d\n", strlen(&p));
	printf("%d\n", strlen(&p + 1));
	printf("%d\n", strlen(&p[0] + 1));
	printf("%d\n", strlen(*p));
	printf("%d\n", strlen(p[0]));

	return 0;
}

(1) 输出结果

3371071-20240201173654724-1902607545.png

(2) 解析

  1. strlen是以'\0'为标志的,字符串默认结束标志位'\0',所以是6
  2. p+1就是第二个元素地址,一直找到内存中的\0',会比1的长度少一,是5
  3. &p是对数组名取地址,代表整个数组的地址,为了存储方便也会以数组首元素的地址表示,所以和1值相同,也是6
  4. &p+1指的是以整个数组的地址为单位,跳过整个数组之后的地址,不知道'\0'在哪,所以是随机值
  5. &p[0]+1就是第二个元素地址,一直找到内存中的\0',会比1的长度少一,是5
  6. *p与p[0]都是第一元素。strlen的参数要传地址进去,否则就会出错

5. 二维数组

int main()
{
	//输出结果?
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a[0][0]));
	printf("%d\n", sizeof(a[0]));
	printf("%d\n", sizeof(a[0] + 1));
	printf("%d\n", sizeof(*(a[0] + 1)));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(*(a + 1)));
	printf("%d\n", sizeof(&a[0] + 1));
	printf("%d\n", sizeof(*(&a[0] + 1)));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a[3]));

	return 0;
}

(1) 输出结果

3371071-20240201173654942-596182727.png

(2) 解析

  1. sizeof(a),这⾥的a是数组名表⽰整个数组,计算整个二维数组的大小,数组每个元素是整型型,一共12个元素,12*4=48
  2. a[0][0]是指二维数组第一个元素,是个整型,大小为4
  3. a[0]相当于第一排首元素的地址,在二维数组中这里可以抽象理解为一维数组的数组名,单独放在sizeof中,所以4*4=16
  4. a[0]+1是一个表达式,代表第一排第二个元素的地址,在×86环境下大小为4,在×64环境下大小为8
  5. a[0]相当于第一排首元素的地址,*a[0]就是首元素,大小为4
  6. a第一排的地址,a+1是第二排地址,在×86环境下大小为4,在×64环境下大小为8
  7. *(a+1)等价于a[1],相当于第二排的数组名,4*4=16
  8. &a[0] + 1等价于a=1,即第二排地址,在×86环境下大小为4,在×64环境下大小为8
  9. *(&a[0] + 1))等价于*(a+1),与7相同,大小为16
  10. *a相当于第一排的数组名,大小也为16
  11. 因为sizeof只是根据类型判断,所以不会管是否越界,所以仍相当于一排的数组名,大小为16
3371071-20240201173655349-530652220.png

6. 指针深度理解

6.1 题目一

#include <stdio.h>
int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}

(1) 输出结果

3371071-20240201173655601-1945681985.png

(2) 解析

  1. a是首元素的地址,a+1是指第二个元素的地址,对其解引用就是第二个元素,也就是2
  2. &a+1跳过整个数组,在被强制类型转换为int*,减1指向5,解引用就是5
3371071-20240201173655772-431934765.png

6.2 题目二

#include<stdio.h>
//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结构是啥?
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

(1) 输出结果

3371071-20240201173656005-956624766.png

(2) 解析

  1. p的地址是0x100000,是十六进制表示,加1跳过一个结构体大小(20),十六进制表示就是00100014
  2. p被强制类型转换为无符号长整型,加1相当于加上数字1,就为00100001
  3. p被强制类型转换为无符号整型的指针,加1跳过一个整型,为00100004

6.3 题目三

#include <stdio.h>
//输出什么?
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}

(1) 输出结果

3371071-20240201173656177-1600637170.png

(2) 解析

  1. 括号表达式从左往右依次计算,取最后一次的值,所以数组中元素简化为1,3,5
  2. p[0]就是数组的首元素,为1

6.4 题目四

//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	return 0;
}

(1) 输出结果

3371071-20240201173656350-1062810610.png

(2) 解析

  1. 首先我们得知道数组在内存中是连续存储的
  2. p的类型为int(*p)[4],p[4][2]等价于*(*(p+4)+2),也就是说把p跳过4个以int(*p)[4]类型的距离,跳过2个整型
  3. 示意图如下,蓝色代表p[4][2],红色代表a[4][2]
3371071-20240201173656553-1373464288.png
  1. &p[4][2] - &a[4][2]之间差四个元素,值为-4,又因为地址是无符号的整数,所以发生整型提升
3371071-20240201173656719-1420140614.png

6.5 题目五

#include <stdio.h>
//输出什么?
int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

(1) 输出结果

3371071-20240201173656925-1900030390.png

(2) 解析

  1. 这是一个指针数组,每个元素类型为char*,分别指向一个字符串,如图
3371071-20240201173657090-113557003.png
  1. pa++跳过一个char*的地址指向第二个元素,对其解引用得到at

6.6 题目六

#include <stdio.h>
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);
	printf("%s\n", *-- * ++cpp + 3);
	printf("%s\n", *cpp[-2] + 3);
	printf("%s\n", cpp[-1][-1] + 1);
	return 0;
}

(1) 输出结果

3371071-20240201173657323-1630276809.png

(2) 解析

  1. 由图可知cpp++指向c+2,再两次解引用得到"POINT"的首元素地址
3371071-20240201173657509-2105360367.png
  1. cpp先++指向c+1,解引用得到c+1,再--得到c解引用得到ENTER的首元素地址,+3得到E的地址
3371071-20240201173657714-909656969.png
  1. *cpp[-2]等价于**(cpp-2),cpp-2指向c+3,两次解引用得到FIRST的首元素地址,+3得到S的地址
3371071-20240201173657891-859701425.png
  1. cpp[1][-1]等价于*(*(cpp-1)-1),这时候cpp指向的是第一个c,cpp-1再解引用得到c+2,再-1解引用得到NEW的地址,最好加1得到E的地址
3371071-20240201173658119-1972632102.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK