sizeof和strlen
sizeof
sizeof是用来计算变量所占内存空间大小的,单位是字节,如果操作数是类型,计算的是使用类型创建的变量所占内存空间的大小。
sizeof只关注占用内存空间的大小,不在乎内存中存放什么数据。
我们来看一下这个代码,它的运行结果是多少?
#include<stdio.h>
int main()
{
int a = 10;
printf("%zu\n", sizeof(a + 3.14));
return 0;
}
a是int类型的数据,3.14是double类型的数据,两者进行运算时,会将int类型的数据提升为double类型,所以最后结果的类型是double类型,所以最后结果应该是8.

strlen()
strlen 是 C 语言库函数,功能是求字符串长度。函数原型如下:
size_t strlen (const char * str);
统计的是从 strlen 函数的参数 str 中这个地址所指向的元素开始向后找,直到\0 之前字符串中字符的个数。
strlen 函数会一直向后找 \0 字符,直到找到为止,所以可能存在越界查找。
看以下代码,运行结果啥?
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[3] = {'a', 'b', 'c'};
char arr2[] = "abc";
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
printf("%d\n", sizeof(arr1));
printf("%d\n", sizeof(arr2));
return 0;
}

arr1是一个字符数组,末尾没有'\0',而strlen会一直向后找'\0',直到找到,所以使用strlen(arr1)的结果应该是一个随机值。
arr2是一个字符串,字符串的末尾含有'\0',所以strlen(arr2)的结果为3。
我们之前说过,sizeof(数组名)中,数组名表示整个数组,计算结果使整个数组的大小。
所以,sizeof(arr1)=3,sizeof(arr2)=4.
sizeof和strlen对比
|-------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| sizeof | strlen |
| 1. sizeof 是操作符 2. sizeof 计算操作数所占内存的大小,单位是字节 3. 不关注内存中存放什么数据 | 1. strlen 是库函数,使用需要包含头文件 string.h 2. strlen 是求字符串长度的,统计的是 \0 之前字符的个数 3. 关注内存中是否有 \0,如果没有 \0,就会持续往后找,可能会越界 |
数组和指针试题
在此之前,还是提醒一下大家:
数组名的意义
- sizeof (数组名),数组名单独放在sizeof()中,这里的数组名表示整个数组,计算的是整个数组的大小。
- & 数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
- 除此之外所有的数组名都表示首元素的地址。
-
题目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;
}

解释:
16 //数组名单独放在sizeof()中,所以计算的是整个数组的大小,结果是4*4=16
8/4 //这里的数组名并没有单独放在sizeof()中,表示的是首元素的地址,a+0表示地址偏移量为0,所以表示的是首元素的地址,结果是指针的大小,即4/8
4 //这里的数组名并没有单独放在sizeof()中,表示首元素的地址,*a得到首元素a[0],这是一个整型变量,大小为4个字节
8/4 //a没有单独放在sizeof()中,表示首元素的地址,a+1表示地址偏移量为1,所以表示的是第二个元素的地址,结果是指针的大小,即4/8
4 //a[1]表示的是数组里面的整型元素,故结果为4
8/4 //&a取出的是整个数组的地址,既然是地址,那就是指针类型的数据,指针大小都是4/8
16 //&a取出的是整个数组的地址,那么它的类型就是数组指针:int(*)[4],那么对&a进行解引用,得到的就是整个数组,整个数组的大小自然就是16
8/4 //&a取出的是整个数组的地址,那么它的类型就是数组指针,&a+1表示要跳过整个数组的大小,但它的类型还是指针,所以结果为4/8
8/4 //a[0]是整型变量,它的地址类型就是整型指针,指针的大小就是4/8
8/4 //&a[0]是第一个元素的地址,&arr[0]+1表示的是第二个元素的地址,指针的大小就是4/8
-
题目2:下面代码的运行结果
#include <stdio.h>
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;
}
这道题跟上面那道题做法几乎相同,我们再来看一下:
6 //数组名单独放在sizeof()中,所以计算的是整个数组的大小,结果是1*6=6
8/4 //这里的数组名并没有单独放在sizeof()中,表示的是首元素的地址,arr+0表示地址偏移量为0,所以表示的是首元素的地址,结果是指针的大小,即4/8
1 //这里的数组名并没有单独放在sizeof()中,表示首元素的地址,*a得到首元素a[0],这是一个整型变量,大小为1个字节
1 //arr[1]表示的是数组里面的整型元素,故结果为1
8/4 //&arr取出的是整个数组的地址,既然是地址,那就是指针类型的数据,指针大小都是 4/8
8/4 //&arr取出的是整个数组的地址,那么它的类型就是数组指针,&a+1表示要跳过整个数组的大小,但它的类型还是指针,所以结果为4/8
8/4 //&arr[0]是第一个元素的地址,&arr[0]+1表示的是第二个元素的地址,指针的大小就是 4/8
-
题目3:下面代码的运行结果
#include <stdio.h>
#include <string.h>int main()
{
char arr[] = { 'a','b','c','d','e','f' };
//1.
printf("%zu\n", strlen(arr));
//2.
printf("%zu\n", strlen(arr + 0));
//3.
printf("%zu\n", strlen(*arr));
//4.
printf("%zu\n", strlen(arr[1]));
5.
printf("%zu\n", strlen(&arr));
6.
printf("%zu\n", strlen(&arr + 1));
7.
printf("%zu\n", strlen(&arr[0] + 1));
return 0;
}
解释:
//这个字符串中没有\0,strlen就会持续往后找\0,就会越界,所以结果是随机值
//arr和arr+0表示的都是首元素的地址,所以在同一测试环境行下,这和第一个结果一样,都是随机值
//*arr表示的是首元素a,a的ASCII码值是97,是一个整型,但是strlen需要接收的是一个字符指针类型的地址,所以会把97当成一个地址,但这块地址可能不属于当前内存,这就造成了非法访问,导致程序崩溃,如果要继续测试下面的代码,就要注释掉这句代码
//和第三个结果一样,会导致程序崩溃,如果要继续测试下面的代码,就要注释掉这句代码
//&arr的类型是数组指针类型:char(*)[],但是它里面存放的值和arr是一样的,而strlen需要接受的是char*类型的指针,所以会把&arr的结果当做char*类型来处理,所以在同一测试环境行下,这和第一个结果一样,都是随机值
//&arr+1表示跳过了整个字符数组,也就是跳过了6个字符元素,但它的类型仍然和&arr类型相同,而strlen需要接受的是char*类型的指针,所以会把&arr+1的结果当做char*类型来处理,然后往后找\0,所以结果是随机值,而且在同一测试环境行下,这和第一个结果相差6(整个字符数组的大小)
//&arr[0]+1表示的是第二个元素的地址,这个字符串中没有\0,strlen就会持续往后找\0,就会越界,所以结果是随机值,而且在同一测试环境行下,这和第一个结果相差1


-
题目4:下面代码的运行结果
#include <stdio.h>
int main()
{
char arr[] = "abcdef";
printf("%zu\n", sizeof(arr));//
printf("%zu\n", sizeof(arr + 0));
printf("%zu\n", sizeof(*arr));
printf("%zu\n", sizeof(arr[1]));
printf("%zu\n", sizeof(&arr));
printf("%zu\n", sizeof(&arr + 1));
printf("%zu\n", sizeof(&arr[0] + 1));
return 0;
}
这个代码的结果分析与前面代码的分析一样,我们就不再赘述,如果有不懂的,欢迎在评论区留言哦。这里我们直接给出答案:
7
8/4
1
1
8/4
8/4
8/4
-
题目5:下面代码的运行结果
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
printf("%zu\n", strlen(arr));
printf("%zu\n", strlen(arr + 0));
printf("%zu\n", strlen(*arr));
printf("%zu\n", strlen(arr[1]));
printf("%zu\n", strlen(&arr));
printf("%zu\n", strlen(&arr + 1));
printf("%zu\n", strlen(&arr[0] + 1));
return 0;
}
//arr表示首元素的地址,由于这是字符串,末尾隐藏着一个\0,所以计算的是\0之前的字符个数,结果是6
//arr+0表示首元素的地址,由于这是字符串,末尾隐藏着一个\0,所以计算的是\0之前的字符个数,结果是6
//和题目三中结果一样,会导致程序崩溃
//会导致程序崩溃
//&arr的类型是数组指针类型,但是它里面存放的值和arr是一样的,而strlen需要接受的是char*类型的指针,所以会把&arr的结果当做char*类型来处理,所以在同一测试环境行下,这和第一个结果一样
//&arr+1表示跳过了整个字符数组,也就是跳过了7个字符元素,但它的类型仍然和&arr类型相同,而strlen需要接受的是char*类型的指针,所以会把&arr+1的结果当做char*类型来处理,然后往后找\0,所以结果是随机值
//&arr[0]+1表示第二个元素的地址,所以结果比第一个的少1,即结果是5
-
题目6:下面代码的运行结果
#include <stdio.h>
int main()
{
char* p = "abcdef";
printf("%zu\n", sizeof(p));
printf("%zu\n", sizeof(p + 1));
printf("%zu\n", sizeof(*p));
printf("%zu\n", sizeof(p[0]));
printf("%zu\n", sizeof(&p));
printf("%zu\n", sizeof(&p + 1));
printf("%zu\n", sizeof(&p[0] + 1));
return 0;
}
解释:
8/4 //p是指针变量,里面存放的是a的地址,所以结果是8/4
8/4 //p+1仍然是指针,里面存放的是b的地址,所以结果是8/4
1 //*p的结果是a,a是字符型变量,所以结果是1
1 //p[0]等价于*(p+0),得到的是a,所以结果是1
8/4 //&p是二级指针,也是地址,所以结果是4/8
8/4 //&p+1仍然是指针,所以结果是4/8
8/4 //&p[0]+1表示的是b的地址,所以结果是4/8
-
题目7:下面代码的运行结果
#include <stdio.h>
#include <string.h>
int main()
{
char* p = "abcdef";
printf("%zu\n", strlen(p));
printf("%zu\n", strlen(p + 1));
printf("%zu\n", strlen(*p));
printf("%zu\n", strlen(p[0]));
printf("%zu\n", strlen(&p));
printf("%zu\n", strlen(&p + 1));
printf("%zu\n", strlen(&p[0] + 1));
return 0;
}

//strlen会往p所指向的那块内存先后找,直到找到\0,所以结果是6
//p+1指向元素b,所以结果是5
//*p得到a,a的ASCII码值是97,strlen会把这个数值当成一个指针,但这个地址不一定在当前程序内,也不一定存在,就造成了非法访问,引起程序崩溃
//p[0]等价于*p,结果与上面一样
//&p是二级指针,strlen会往&p所指向的那块内存先后找,直到找到\0,但是不知道\0在那块空间的位置,所以结果是随机值
//和上面一样,结果是随机值,但是这里有一个问题,strlen(&p)和strlen(&p+1)的结果是否有关系呢?答案是没有关系,因为你不知道从p往后找是否能在走完p所对应的内存之前找到\0
//&p[0]+1表示b的地址,所以结果是5
-
题目8:下面代码的运行结果
#include <stdio.h>
int main()
{
int a[3][4] = { 0 };
printf("%zu\n", sizeof(a));
printf("%zu\n", sizeof(a[0][0]));
printf("%zu\n", sizeof(a[0]));
printf("%zu\n", sizeof(a[0] + 1));
printf("%zu\n", sizeof((a[0] + 1)));
printf("%zu\n", sizeof(a + 1));
printf("%zu\n", sizeof((a + 1)));
printf("%zu\n", sizeof(&a[0] + 1));
printf("%zu\n", sizeof(*(&a[0] + 1)));
printf("%zu\n", sizeof(*a));
printf("%zu\n", sizeof(a[3]));
return 0;
}

解析:
二维数组是元素为一维数组的数组
48 //a是数组名,数组名单独放在sizeof()中,计算的是整个数组的大小,所以结果是4*12=48
4 //a[0][0]表示的是数组中的整型元素,所以结果是4
16 //a[0]表示的是第一行一维数组的数组名,数组名单独放在sizeof中,计算的是整个数组的大小,所以结果是4*4=16
4/8 //a[0]表示第一行一维数组的数组名,数组名并没有单独放在sizeof中,所以表示的是第一行一维数组首元素的地址,所以a[0]+1表示的是第二个元素的地址,既然是地址,那大小就是4/8
4 //a[0]+1表示的是第一行的一维数组中第二个元素的地址,那么*(a[0]+1)就是访问数组中元素,所以结果为4
4/8 //a表示二维数组的数组名,数组名并没有单独放在sizeof中,表示的是第一行一维数组的地址,a+1表示要跳过第一行整个一维数组,指向第二行,即表示第二行一维数组的地址,既然是地址,那大小就是4/8
16 //a+1表示第二行一维数组的地址,*(a+1)表示访问整个一维数组,既然是访问整个一维数组,结果自然是4*4=16(也可以这么理解:*(a+1)等价于a[1],a[1]表示的是第二行一维数组的数组名,数组名单独放在sizeof中,结果就是计算这个一维数组的大小)
4/8 //a[0]表示第一行一维数组的数组名,前面有&,取出的是整个一维数组的地址,所以&arr[0]+1表示跳过第一行整个一维数组,指向第二行整个一维数组,既然还是地址,那么结果就是4/8
16 //&a[0]+1表示第二行整个一维数组的地址,对它进行解引用,访问的是整个一维数组,所以结果是16
16 //a表示二维数组的数组名,数组名并没有单独放在sizeof中,表示的是第一行一维数组的地址,对这个地址进行解引用,访问的是整个一维数组,所以结果是16(也可以这么理解:*a等价于*(a+0)等价于a[0],表示的是第一行一维数组的数组名,数组名单独放在sizeof中,访问的是整个数组,所以结果是16)
16 //看到这个代码,想必很多小伙伴都认为这个包会报错的,毕竟,这不是越界访问了吗?但是,我们说过,sizeof在计算变量大小的时候,是通过类型求推导的,所以他并不会真正去访问内存,既然没有越界访问内存,那不就不会报错了,而在根据类型推导sizeof(a[3])的大小时,会将a[0]和a[3]是同等类型的,所以结果自然是16了
指针运算试题
-
题目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;
}

解析:a是数组名,一般情况下表示首元素的地址,a+1表示跳过一个整型的大小,所以a+1指向第二个元素,则*(a+1)得到的是2;&a取出的是整个数组的的地址,所以&a+1会跳过整个整形数组,如图,将这个地址转换成整型指针的地址以后赋给ptr,ptr指向如图所示,ptr-1会向前走过一个整形大小的步长,如图所示,所以*(ptr-1)得到的是5
-
题目2:下面代码的运行结果
//在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;
}
解析:在定义结构体的同时创建了结构体指针变量p,并将0X100000强制类型转换成结构体指针类型以后赋值给p,p+0x1也就是p+1,p所指向的变量为结构体类型,所以p+1需要跳过20个字节,所以p+1=0x100020,等等,这对吗?注意咯,这里是十六进制的形式,逢十六进一,所以结果应该是0x100014,而在X86环境下,会将地址的8位都打印出来,且不会省略掉前面的0,所以结果是00100014.
将p强制类型转换成unsigned long类型以后,里面的值就是一个0x100000,加上一后就是普通的整型相加,所以结果是0X100001,在X86环境下的打印结果是0X100001
将p强制类型转换成unsigned int*类型以后,对指针+1会跳过4个字节,所以结果就是0X100004,在X86环境下,结果就是00100004
-
题目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;
}
解析:我们先要搞清楚数组里面放了啥,数组里面放的是0,1,2,3,4,5嘛?看着好像是的。注意啦兄弟们,数组里面用括号括起来的是逗号表达式哦,所以数组里面的元素应该是{1,3,5,0,0,0}。
弄清这个以后,我们再来看指针。p是一个整形指针,a[0]是第一行那个一维数组的数组名,一般情况下,他表示首元素的地址,也就是说p里面存放的是第一行一维数组首元素的地址,p[0]等价于*(p+0),p+0还是表示第一行一维数组首元素的地址,对它进行解引用,访问的就是第一行一维数组的首元素,也就是1,所以打印结果是1.
-
题目4:下面代码的运行结果
#include <stdio.h>
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}

aa表示的是二维数组的名称,一般情况下,数组名表示首元素的地址,则aa表示第一行一维数组的地址,aa+1表示跳过整个一维数组,则aa+1指向第二行整个一维数组;&aa取出的是整个二位数组的地址,&aa+1跳过的是整个二维数组,由此,可得图中各指针指向的由来。
ptr1和ptr2都是整型指针,+1时跳过一个整形元素的步长,-1时向前走一个整形元素的步长,所以得到相应指向,对指针解引用以后可以得到打印结果是:10,5
-
题目5:下面代码的运行结果
//假设环境是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;
}
图示解析:

-
题目6:下面代码的运行结果
#include <stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
图示解析:

-
题目7:下面代码的运行结果
#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;
}
图示解析:

运行结果: