前言:
大家在初步学习C语言的时候,可能对于sizeof和strlen的使用有一些混淆,今天我用一篇文章带大家学懂sizeof和strlen。
a.sizeof既是C语言的关键字也是C语言的操作符,用来计算括号里的变量sizeof(a)或者变量类型sizeof(int)所占内存空间大小,单位是字节。
int main()
{
int i = 10;
int arr[] = { 1, 2 };
int arr[10] = { 1, 2 };
size_t sz = sizeof(arr);//size_t是专门为sizeof设计的变量类型,返回值是unsigned int
printf("%zd\n", sizeof(i));//变量
printf("%zd\n", sizeof(int));//变量类型
printf("%zd\n", sz);//%zd是专门针对size_t的占位符,8
printf("%zd\n", sizeof(arr1));//40,如果已经定义了数组长度,那就按照定义的来求。
return 0;
}
b.sizeof关注的是括号里的表达式的值属性,也就是根据类型决定sizeof的值,sizeof里面如果是一个计算的式子,这个式子都不会执行,因为在编译链接的时候已经把sizeof里面的类型确定了,在可执行文件里计算时,不执行这个式子。
int main()
{
short a = 10;
printf("%zd\n", sizeof(a = a + 10));//由类型决定,2
printf("%d\n", a);//10
}
为了更加生动,我给大家看一下这个程序的汇编语言,也就是编译连接后的语句。
我们可以看到在变成可执行.exe文件后,sizeof的括号里面已经变成了2,不需要再进行算术计算了,a也就是10。
c.重中之重:
arr数组名代表的是数组首元素地址,只有两种特殊情况,sizeof(arr)和&arr,一个是求整个数组所占内存空间大小,另一个是取整个数组的地址。
a.strlen是库函数,头文件是#include
size_t strlen(const char* p);
b.stlen是从这个函数参数p向后一直寻找到’\0’为止,统计出’\0’之前的字符数。一个字符一个字符读,也就是1个字节一个字节读。
c.因为会一直向后找到’\0’为止,所以可能会存在越界访问,这也就解释了我上文为什么说strlen求整型数组得到的结果也可以解释。
我们来看一组对比,就更加清晰了。
#include
int main()
{
char arr1[3] = {'a', 'b', 'c'};
char arr2[] = "abc";
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));//3
printf("%d\n", sizeof(arr1));//3
printf("%d\n", sizeof(arr2));//4,因为还有一个'\0'
return 0;
}
至于第一个35,这个在不同的设备,不同的IDE上一定是不同的,因为内存分配是不一样的,我们可以看下面的这张内存分配图,每一行是八个字节,起始的字节就是数组的首元素,我们可以一直向后找到00,00之前一共有35个字节,所以答案是35。
有了上面的经验,相信大家也对于strlen为什么不能求整型数组的长度明白了,举个例子。
int main()
{
int arr1[] = { 1, 2, 3 };
int arr2[] = { 0, 1, 2 };
printf("%zd\n", strlen(arr1));//1
printf("%zd\n", strlen(arr2));//0
return 0;
}
解释:
第一个数组排列:X64平台上,01 00 00 00 00 00 00 00,当读到第二个字节00就结束了,所以是1。
第二个数组首字节直接就是0,所以是0。
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//整个数组所占内存空间大小,4 * 4 = 16
printf("%d\n", sizeof(a + 0));//a是首元素地址,所以是求指针变量的大小,4/8
printf("%d\n", sizeof(*a));//*a = 1, 所以是4
printf("%d\n", sizeof(a + 1));//指针变量,4/8
printf("%d\n", sizeof(a[1]));//a[1] = 2,4
printf("%d\n", sizeof(&a));//地址,也是指针变量,4/8
printf("%d\n", sizeof(*&a));//sizeof(a),16
printf("%d\n", sizeof(&a + 1));//跳过了一个数组的地址,4/8
printf("%d\n", sizeof(&a[0]));//4/8
printf("%d\n", sizeof(&a[0] + 1));//&arr[1],4/8
return 0;
}
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//6
printf("%d\n", sizeof(arr+0));//4/8
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8
printf("%d\n", sizeof(&arr+1));//4/8
printf("%d\n", sizeof(&arr[0]+1));//4/8
printf("%d\n", strlen(arr));//随机值,因为目前不确定到哪里遇到字节00
printf("%d\n", strlen(arr+0));//随机值,和上一个一样
printf("%d\n", strlen(*arr));//无法输出,非法访问,访问冲突,*arr = a,访问以a的ASCII码值97为地址既地址为0000000000000061的内存空间,但是其实是没有权限访问的
printf("%d\n", strlen(arr[1]));//无法输出×,下文都以此代表访问冲突
printf("%d\n", strlen(&arr));//随机值,和strlen(arr)是一个值
printf("%d\n", strlen(&arr+1));//随机值,比strlen(arr)小6
printf("%d\n", strlen(&arr[0]+1));//随机值,比strlen(arr)小1
return 0;
}
为了方便看清运行结果,我将其中两个访问冲突的先注释掉了,我们直接来验证运行结果。
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//7,有'\0'
printf("%d\n", sizeof(arr + 0));//4/8
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8
printf("%d\n", sizeof(&arr + 1));//4/8
printf("%d\n", sizeof(&arr[0] + 1));//4/8
printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr + 0));//6
printf("%d\n", strlen(*arr));//×
printf("%d\n", strlen(arr[1]));//×
printf("%d\n", strlen(&arr));//6,指针取得也是第一个元素的地址
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//5
return 0;
}
3.这是最难的字符串数组的题
int main()
{
char* p = "abcdef";
printf("%d\n", sizeof(p));//4/8
printf("%d\n", sizeof(p + 1));//4/8
printf("%d\n", sizeof(*p));//1,*p解引用出来的就是a,这是字符指针变量的用法,p直接用才是字符串
printf("%d\n", sizeof(p[0]));//p[0]就是a,所以是1
printf("%d\n", sizeof(&p));//4/8
printf("%d\n", sizeof(&p + 1));//4/8
printf("%d\n", sizeof(&p[0] + 1));//4/8
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p + 1));//5
printf("%d\n", strlen(*p));//×,*p = a,错误
printf("%d\n", strlen(p[0]));//× p[0] = a ,错误
printf("%d\n", strlen(&p));//随机值
printf("%d\n", strlen(&p + 1));//随机值
printf("%d\n", strlen(&p[0] + 1));//随机值
return 0;
}
本题总结:
1.使用了字符指针变量存放常量字符串这种用法,有关这种用法我在上一篇博客,链接:link中详细讲解了。
2.针对上述三个题,我们一定要谨记,strlen里面接收的参数一定要是地址!!!
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//48
printf("%d\n", sizeof(a[0][0]));//4
printf("%d\n", sizeof(a[0]));//第一行数组,16
printf("%d\n", sizeof(a[0] + 1));//4/8,第一行数组第二个元素的地址
printf("%d\n", sizeof(*(a[0] + 1))); //4
printf("%d\n", sizeof(a + 1));//4/8,第二行数组的地址
printf("%d\n", sizeof(*(a + 1)));//16,第二行数组
printf("%d\n", sizeof(&a[0] + 1));//4/8,第二行数组的地址
printf("%d\n", sizeof(*(&a[0] + 1))); //16
printf("%d\n", sizeof(*a));//第一行数组,16
printf("%d\n", sizeof(a[3]));//16
return 0;
}
1.后面的例题涉及到了一部分指针和数组名的使用,总之记住一句话,除了sizeof(arr)和&arr这两种一模一样的形式时,数组名代表的是整个数组的地址(包括sizeof(*arr)这样也不属于这两种特殊类型),其余数组名代表的都是首元素地址。
2.其实sizeof和strlen本质上是完全不同的,只不过他们都与长度、数组和指针联系起来了。而我们需要掌握的就是他们在各种不同用法之间抓住区别。