指针篇(6)- sizeof和strlen,数组和指针笔试题

发布于:2025-06-30 ⋅ 阅读:(15) ⋅ 点赞:(0)


一、sizeof和strlen的对比

1.1 sizeof

sizeof在这篇文章里写过一部分,可通过目录查找
这篇的数组名的理解部分也有写

1.2 strlen

strlen在这里有写过一些内容
这里再详细说一下

strlen是C语言库函数,功能是求字符串长度,函数原型如下

size_t strlen(const char* str)

统计的是从strlen函数的参数str中这个地址开始向后,\0之前字符串中字符的个数。
strlen函数会一直向后找\0字符,直到找到为止,所以可能存在越界查找,即下图所示:
在这里插入图片描述
可以看出,这里字符数组里由于没有\0,算出了一个随机值,在内存中是这样的:
在这里插入图片描述
strlen在数完a,b,c后就一直向后找\0,但\0出现的位置不确定,所以打印出了这样一个值。

再看一个程序:

在这里插入图片描述
在这里插入图片描述
打印结果是不是如你所想呢?
sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,这里是字符数组,每个元素是一个字节的字符,也就是实际计算是3×1(1字节)。

1.3 sizeof和strlen的对比

在这里插入图片描述


二、数组和指针笔试题解析

在这里插入图片描述
图示指针3文章的内容,这里会用到
还需要指针1内存的知识
在这里插入图片描述

2.1 一维数组

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%zu\n", sizeof(a));
	printf("%zu\n", sizeof(a + 0));
	printf("%zu\n", sizeof(*a));
	printf("%zu\n", sizeof(a + 1));
	printf("%zu\n", sizeof(a[1]));
	printf("%zu\n", sizeof(&a));
	printf("%zu\n", sizeof(*&a));
	printf("%zu\n", sizeof(&a + 1));
	printf("%zu\n", sizeof(&a[0]));
	printf("%zu\n", sizeof(&a[0] + 1));
	return 0;
}

问输出结果是什么?
在这里插入图片描述
这是x64环境下的输出结果。

这里挨个提出来看,下面的/就是或的意思:

printf("%zu\n", sizeof(a));

输出结果:16
a是数组名,数组名单独放在sizeof中,数组名表示整个数组,计算的是整个数组的大小,单位是字节。

printf("%zu\n", sizeof(a + 0));

输出结果:4/8
a是数组名,并没有单独放在sizeof内部,也没有&,所以a就是首元素的地址。a+0,还是首元素的地址,sizeof(a+0)计算的是一个地址的大小。

注意:数组的地址就是数组最开始位置的地址,数组首元素地址也是那个地址,地址就是指针,在指针1中写过,指针变量的大小与类型无关,就是4/8个字节,只与运行环境有关。

printf("%zu\n", sizeof(*a));

输出结果:4
a是数组名,并没有单独放在sizeof内部,也没有&,所以a就是首元素的地址。*a就是首元素==a[0],就是整数1,sizeof(*a)就是4个字节。

printf("%zu\n", sizeof(a + 1));

输出结果:4/8
a就是首元素的地址,a+1就是第二个元素的地址,sizeof(a+1)计算的是地址的大小。

printf("%zu\n", sizeof(a[1]));

输出结果:4
a[1]就是数组的第二个元素,大小是4个字节。

printf("%zu\n", sizeof(&a));

输出结果:4/8
&a-取出的是数组的地址,数组的地址也是地址,是地址大小就是4/8个字节
&a的特殊性体现在±整数
int (*p)[4] = &a

printf("%zu\n", sizeof(*&a));

输出结果:16
&a-取出的是数组的地址,他的类型是int(*)[4],对于数组指针解引用,访问的是这个数组,大小就是16个字节。
*&a 等同于 a,sizeof( *&a) 等同于 sizeof(a)

printf("%zu\n", sizeof(&a + 1));

输出结果:4/8
&a是数组的地址,&a+1是跳过数组后的地址,是地址就是4/8个字节

printf("%zu\n", sizeof(&a[0]));

输出结果:4/8
a[0]是第一个元素,&a[0]就是第一个元素的地址,大小就是4/8

printf("%zu\n", sizeof(&a[0] + 1));

输出结果:4/8
&a[0]就是第一个元素的地址,&a[0]+1就是第二个元素的地址,大小就是4/8。


2.2 字符数组

代码1:

int main()
{
    char arr[] = { 'a','b','c','d','e','f' };

    printf("%zu\n", sizeof(arr));//6, arr表示整个数组,sizeof(arr)计算的是整个数组的大小
    //6个元素,每个元素是一个char,一个char就是一个字节,一共占6个字节
    printf("%zu\n", sizeof(arr + 0));//4/8, arr就是首元素的地址,只要是地址大小就是4/8个字节
    printf("%zu\n", sizeof(*arr));//1,arr就是首元素的地址,*arr就是首元素,sizeof(*arr)计算的是首元素的大小
    printf("%zu\n", sizeof(arr[1]));//1, arr[1]是第二个元素,大小就是1个字节
    printf("%zu\n", sizeof(&arr));//4/8,&arr取出的是数组的地址,数组的地址也是地址,大小就是4/8个字节
    printf("%zu\n", sizeof(&arr + 1));//4/8, &arr取出的是数组的地址,&arr+1是跳过数组后的地址,跳过去后还是地址,大小就是4/8个字节
    printf("%zu\n", sizeof(&arr[0] + 1));//4/8,&arr[0] + 1是数组第二个元素的地址,大小就是4/8个字节
    return 0;
}

解释都在代码里,这里只补充一下倒数第二个

printf("%zu\n", sizeof(&arr + 1));//4/8, &arr取出的是数组的地址,&arr+1是跳过数组后的地址,跳过去后还是地址,大小就是4/8个字节

跳过之后(&arr+1)和&arr的类型是一样的,跳过之后还是一个指向6个元素的一个数组的地址,因为&arr取出的就是一个6个字符的数组的地址,+1之后类型不变,还是一个数组指针。

代码2

int main()
{
    char arr[] = { 'a','b','c','d','e','f' };
    printf("%zu\n", strlen(arr));//随机值,arr是数组首元素的地址,字符串中没有\0
    printf("%zu\n", strlen(arr + 0));//随机值,arr是数组首元素的地址,arr+0还是首元素的地址,字符串中没有\0
    //printf("%zu\n", strlen(*arr));//arr是数组首元素的地址,*arr是首元素-'a'-97
    //非法访问内存 - 程序就会崩溃
    //printf("%zu\n", strlen(arr[1]));//err,非法访问内存,崩溃,arr[1] == 'b' == 98
    printf("%zu\n", strlen(&arr));//随机值,&arr是数组的地址,从数组的地址也就是数组的起始位置开始向后数字符串的长度,要找\0,但是没有\0
    //所以是随机值
    printf("%zu\n", strlen(&arr + 1));//随机值,&arr+1是跳过整个数组后的地址,向后找\0,也是随机值
    printf("%zu\n", strlen(&arr[0] + 1));//随机值,&arr[0] + 1是第二个元素的地址
    return 0;
}

这里单独拿几个出来看:

printf("%zu\n", strlen(*arr));//arr是数组首元素的地址,*arr是首元素-'a'-97
    //非法访问内存 - 程序就会崩溃

字符a的ASCII值为97,把97作为地址传给了strlen,97这样一个随机值的地址不能被直接访问,这里可以调试看一下:
在这里插入图片描述
61为16进制的61,转化为10进制就是97了

接下来看这个:

printf("%zu\n", strlen(&arr));//随机值,&arr是数组的地址,从数组的地址也就是数组的起始位置开始向后数字符串的长度,要找\0,但是没有\0
    //所以是随机值

&arr和arr的地址在数组中的起始位置是一样的
在这里插入图片描述
调用strlen函数时,传arr地址时,传过去的地址类型为char*,传&arr的地址时,传的地址类型为char*[6],两者传过去都会转化为const char* s这样更为严谨的地址形式,虽然传的地址不同,但得到的地址是一样的。

这里的随机值比最初的arr地址打印出的随机值少6,因为跳过了6个字符。

printf("%zu\n", strlen(&arr + 1));//随机值,&arr+1是跳过整个数组后的地址,向后找\0,也是随机值

(&arr + 1)比arr地址的随机值少1,跳过了a。
打印结果验证:
在这里插入图片描述
正如预想
在这里插入图片描述
报警告的原因也正如前面所说,传的地址类型不同。

接着来看,还有很多

代码3

#include <stdio.h>
int main()
{
    char arr[] = "abcdef";
    printf("%zu\n", sizeof(arr));//7
    printf("%zu\n", sizeof(arr + 0));//4/8
    printf("%zu\n", sizeof(*arr));//1
    printf("%zu\n", sizeof(arr[1]));//1
    printf("%zu\n", sizeof(&arr));//4/8
    printf("%zu\n", sizeof(&arr + 1));//4/8
    printf("%zu\n", sizeof(&arr[0] + 1));//4/8
    return 0;
}

代码4

#include <stdio.h>
#include <string.h>
int main()
{
    char arr[] = "abcdef";
    printf("%zu\n", strlen(arr));//6
    printf("%zu\n", strlen(arr + 0));//6
    //printf("%zu\n", strlen(*arr));//err
    //printf("%zu\n", strlen(arr[1]));//err
    printf("%zu\n", strlen(&arr));//6
    printf("%zu\n", strlen(&arr + 1));//随机值
    printf("%zu\n", strlen(&arr[0] + 1));//5
    return 0;
}

在这里插入图片描述
附加一张图加以理解。

代码5

int main()
{
    char* p = "abcdef";
    printf("%zu\n", sizeof(p));//4/8, p是指针变量
    printf("%zu\n", sizeof(p + 1));//4/8, p+1就是b的地址
    printf("%zu\n", sizeof(*p));//1
    printf("%zu\n", sizeof(p[0]));//1, p[0]->*(p+0) -> *p
    printf("%zu\n", sizeof(&p));//4/8, &p是指针变量p的地址,是一个二级指针
    printf("%zu\n", sizeof(&p + 1));//4/8
    printf("%zu\n", sizeof(&p[0] + 1));//4/8, &p[0] + 1是b的地址
    return 0;
}

想要写对这个题,就先要看懂这串代码:

char* p = "abcdef";

这里并不是把abcdef放到p里去,这句代码对应的内存布局如图:
在这里插入图片描述
其实有两块内存,一块里放a b c d e f \0,这里把首字符a的地址给p,内存布局知道了就可以对着我给的答案来写了。

需要注意的一点是倒数第二个

printf("%zu\n", sizeof(&p + 1));//4/8

(&p+1)不是b的地址,这两个空间是无关的

代码6

int main()
{
    char* p = "abcdef";
    printf("%zu\n", strlen(p));//6
    printf("%zu\n", strlen(p + 1));//5
    //printf("%zu\n", strlen(*p));//err
    //printf("%zu\n", strlen(p[0]));//err
    printf("%zu\n", strlen(&p));//随机值,p的内容不确认,p变量在内存中后续的空间内容不确定
    printf("%zu\n", strlen(&p + 1));//随机值
    printf("%zu\n", strlen(&p[0] + 1));//5

    return 0;
}

唯一要说的就是

printf("%zu\n", strlen(&p));//随机值,p的内容不确认,p变量在内存中后续的空间内容不确定

&p指向的是p的地址,里面是4个还是8个字节有平台环境决定,每个字节里放的什么也不知道,不知道\0是在空间内还是在空间外,不知道什么时候遇到\0,所以是随机值。
附加内存布局图:
在这里插入图片描述

2.3 二维数组

在写代码之前先说一点,二维数组的每一行是一个一维数组,一维数组的数组名如图(也是下面代码的内存布局):
在这里插入图片描述

int main()
{
    int a[3][4] = { 0 };
    printf("%zu\n", sizeof(a));//48
    //3×4=12个整型,一个整型4个字节,一共48个字节
    printf("%zu\n", sizeof(a[0][0]));//4
    printf("%zu\n", sizeof(a[0]));//16,a[0]是第一行这个一维数组的数组名,单独放在sizeof内部了,a[0]表示第一行这个数组
    //sizeof(a[0])计算的是第一行的大小
    printf("%zu\n", sizeof(a[0] + 1));//4/8, a[0]就是第一行第一个元素的地址==&a[0][0], a[0]+1是第一行第二个元素的地址
    printf("%zu\n", sizeof(*(a[0] + 1)));//4, *(a[0] + 1)是第一行第二个元素
    printf("%zu\n", sizeof(a + 1));//4/8, a是二维数组的数组名,这里只能表示数组首元素的地址,也就是第一行的地址
    //a+1 就是第二行的地址
    //a --> int(*)[4]
    printf("%zu\n", sizeof(*(a + 1)));//16,*(a + 1) == a[1]
    //a[1]就是第2行的数组名,计算的是整个数组的大小
    printf("%zu\n", sizeof(&a[0] + 1));//4/8
    //a[0]是第一行的数组名
    //&a[0]取出的是第1行的地址
    //&a[0] + 1是第2行的地址
    printf("%zu\n", sizeof(*(&a[0] + 1)));//16
    printf("%zu\n", sizeof(*a));//16,a是二维数组的数组名,这里只能表示数组首元素的地址,也就是第一行的地址
    //*a 就是第一行
    printf("%zu\n", sizeof(a[3]));//16,不会有越界访问的
    //sizeof在计算变量,数组的大小的时候,是通过类型来推导的,不会真实去访问内存空间
    return 0;
}

这里就重点说一下最后一个

printf("%zu\n", sizeof(a[3]));

在这里插入图片描述

你是不是认为不存在a[3],会越界访问呢?
其实不然,实际上也没有第4行a[3],也没有真正的去访问a[3]这一行,而是通过推导得到的16字节。第四行的数组名放到这里,通过类型就知道是个int[4]的数组。

空口无凭,接下来解释为什么sizeof在计算变量,数组的大小的时候,是通过类型来推导的,不会真实去访问内存空间。
在这里插入图片描述
这里按照通常思路,s=10+2=12,sizeof也是int+int=int。但结果却出现差错,这里硬把int,4个字节的类型放到2个字节的类型short里,这里发生了截断。这个表达式的最终结果是s说了算。而且sizeof里的表达式也确实是不会完成计算的。

这是因为代码在经过图示途径才能生成可执行程序
在这里插入图片描述
刚刚不计算s=a+2这个表达式的原因是,代码在编译,链接的期间,编译器就分析short=int+int,表达式最终s说了算,表达式的最终类型为short类型,即sizeof(short)=2是在编译阶段发现的。这时已经是个数字2了,在运行代码的时候就没有(sizeof(s=a+2))这个表达式了,在运行时就不会执行s=a+2,那时候都不是C语言代码了,已经变成二进制指令,直接就是个2了,打印的时候自然也打印个2出来。

一句话来说就是表达式s=a+2的执行是在运行期间执行的,而运行期间该表达式已经无了。


总结

以上就是指针6的内容了,指针7将继续写一些指针运算相关的笔试题,不得不说期末周考完就是爽啊,安安心心学编程搞文章舒服多了。喜欢的靓仔靓女们不要忘记一键三连给予支持哦~