sizeof和strlen总结

本文详细介绍了C语言中的sizeof和strlen两个关键操作,通过举例分析了它们在整形一维数组、字符型一维数组、整形二维数组以及其他情况下的应用。特别指出,strlen适用于字符串,依赖于''作为结束标志,而对于非字符串数组,使用strlen可能会导致未定义行为。


前言

sizeof和strlen的使用是C语言面试的必考考点,并且对于初学者属实不太友好,稍有不慎就会踩坑。昨日接触了一些题目,特地总结一下这类题目的解题方法。


一、整形一维数组

示例:

	int arr[] = { 1, 2, 3, 4 };
	printf("%d\n", sizeof(arr)); // 16

定义了一个元素个数为4的整形数组,sizeof(arr)显然是求数组所占的空间大小,由于一个整型变量占用4个字节,故整个数组占用4*4=16个字节,打印输出16。

那么以下语句分别打印输出什么呢?

	printf("%d\n", sizeof(arr + 0)); // 4 求 sizeof(int*)
	printf("%d\n", sizeof(*arr)); // 4, 求 sizeof(int)
	printf("%d\n", sizeof(arr + 1)); // 4, 求 sizeof(int*)
	printf("%d\n", sizeof(arr[1])); // 4, 求 sizeof(int)
	printf("%d\n", sizeof(&arr));// 4, 求 sizeof(int(*)[4]);

arr+0会触发数组隐式转换成为指向首元素的指针,所有实际上是求int*类型的大小,由于使用32位编译,指针大小为4,故打印4。

*arr会触发数组隐式转换成为指向首元素的指针,这个指针进行解引用,得到的是数组的第一个元素1,故打印出int类型的大小,即4。

arr+1与arr+0同理,打印4。

arr[1]就是数组的第二个元素,同样是输出int类型的大小打印4。

&arr先隐式转换成指针,再取地址,得到数组指针int(*)[4],但是由于指针的大小均为4个字节,故打印4。

再看以下代码:

	// 先算 &arr, 得到了数组指针 int(*)[4], 再解引用, 得到了 int[4]
	printf("%d\n", sizeof(*&arr));// 16, 求 sizeof(int[4]);
	// 先算 *arr, arr 会隐式转正首元素的指针(int*), 解引用得到了 int
	// 再来取地址得到了 int*
	printf("%d\n", sizeof(&*arr));// 4, 求 sizeof(int*)

	printf("%d\n", sizeof(&arr + 1)); // 4, 求 sizeof(int(*)[4])
	printf("%d\n", sizeof(&arr[0])); // 4, 求 sizeof(int*)
	printf("%d\n", sizeof(&arr[0] + 1)); // 4, 求 sizeof(int*)

对于*&arr而言,先取地址,得到数组指针int(*)[4],再解引用得到int[4],也就是数组arr本身,等价于sizeof(arr),故打印16。

对于&*arr而言,先隐式转换,再解引用,得到数组首元素,类型为int,再取地址得到int*,故打印指针大小4。

&arr + 1,先隐式转换,再取地址得到数组指针int(*)[4],+1不影响类型,故打印4。

&arr[0],先得到数组第一个元素int,再取地址得到int*,故打印4。
&arr[0] + 1同理,+1不影响数据类型,故打印4。

二、字符型一维数组

	char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
	printf("%d\n", sizeof(arr)); // 6, sizeof(char[6])
	printf("%d\n", sizeof(arr + 0)); // 4, sizeof(char*)
	printf("%d\n", sizeof(*arr)); // 1, sizeof(char)
	printf("%d\n", sizeof(arr + 1)); // 4
	printf("%d\n", sizeof(arr[1]));// 1, sizeof(char)
	printf("%d\n", sizeof(&arr));// 4, sizeof(char(*)[6])
	printf("%d\n", sizeof(*&arr)); // 6, sizeof(char[6])
	printf("%d\n", sizeof(&*arr));// 4, sizeof(char*)
	printf("%d\n", sizeof(&arr[1] + 1)); // 4, sizeof(char*)
	printf("%d\n", sizeof(&arr + 1)); // 4, sizeof(char(*)[6]);

分析过程与上文类似,不再赘述。

那么,如果将sizeof换成strlen,结果是否会有不同呢?

	// 此处的操作是未定义行为. arr 就不是一个 "字符串" 
	// 必须是带有 \0 的 char 数组才是字符串. 
	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));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));

上面的代码乍一看有板有眼,但是其实都是“未定义行为”。由于strlen根据’\0’作为字符串的结束标志,而arr里并没有’\0’,换句话说,可以认为arr根本就不是一个字符串,所以任何求字符串长度的操作所得到的结果都没有意义。

注:“未定义行为”是指C语言标准未做规定的行为,运行结果不可预期(可能输出一个值、不输出或者程序崩溃等等)且没有任何意义。

根据以上结论,我们定义并且初始化一个正常的字符串,再作讨论。代码如下:

	char arr[] = "abcdef";
	printf("%d\n", strlen(arr)); // 6
	printf("%d\n", strlen(arr + 1)); // 5
	printf("%d\n", strlen(&arr + 1)); // 类型不匹配
	printf("%d\n", strlen(&arr)); // 6  类型不匹配
	printf("%d\n", strlen(&arr[0])); // 6
	printf("%d\n", strlen(*&arr)); // 6
	printf("%d\n", strlen(&*arr)); // 6
	printf("%d\n", strlen(&arr[1] + 1));

strlen输出字符串的有效长度,不包括’\0’,本例中字符串长度为6,故第一条语句输出6。

arr+1而言,先隐式转换成为char*,再+1,得到的指针指向字符串的第二个元素’b’,再进行求长度操作,那么从’b’到’f’,长度为5,故输出5。

&arr + 1,得到char(*)[6],而strlen的参数必须为char*,故类型不匹配。&arr同理。

&arr[0],得到char*,指向首元素,求长度得到6。

*&arr,char*=>char(*)[6]=>char[6],也就是arr本身,等价于strlen(arr),故输出6。

&*arr,得到char*,不影响求长度,输出6。

&arr[1] + 1,得到char*,本来指向’b’,+1后指向’c’,故长度为4,输出4。

再深入探究,如果使用一个指针指向这个字符串,再进行以上操作,会发生什么?

	char* p = "abcdef";
	//printf("%d\n", sizeof(p)); // 4 sizeof(char*)
	//printf("%d\n", sizeof(p + 1)); // 4 sizeof(char*)
	//printf("%d\n", sizeof(*p)); // 1 sizeof(char)
	printf("%d\n", sizeof(p[0])); // 1 sizeof(char)
	//printf("%d\n", sizeof(&p)); // 4 sizeof(char**)
	//printf("%d\n", sizeof(&p[1] + 1)); // 4 sizeof(char*)
	//printf("%d\n", sizeof(*&p)); // 4 sizeof(char*)
	//printf("%d\n", sizeof(&*p)); // 4 sizeof(char*)

	printf("%d\n", strlen(p)); // 6
	printf("%d\n", strlen(p + 1)); // 5
	printf("%d\n", strlen(*p)); // 类型不匹配
	printf("%d\n", strlen(p[1])); // 类型不匹配
	printf("%d\n", strlen(&p)); // 类型不匹配. 
	printf("%d\n", strlen(&p[1] + 1)); // 4
	printf("%d\n", strlen(*&p)); // 6
	printf("%d\n", strlen(&*p)); // 6

不再赘述sizeof,需要注意的是p[0],p是char*类型,p[0]相当于混用[],就是指首元素,所以转为char类型,sizeof(char)输出1。

strlen( p ),p指向首元素,故求长度为6。

p+1,指向第二个元素’b’,求长度为5。

*p得到char,不匹配char*。p[1]同理。

&p得到char**,不匹配。

&p[1] + 1,指向’c’,求长度为4。

*&p,char*取地址得到char**,再解引用得到char*,求长度为6。
&*p,char*解引用得到char,再取地址得到char*,输出6。

三、整形二维数组

讨论完了一维数组,我们来研究二维数组的情况。

	int arr[3][4] = { 0 };
	printf("%d\n", sizeof(arr)); // 48  sizeof(int[3][4])
	printf("%d\n", sizeof(arr[0])); // 16 sizeof(int[4])
	//arr[0] int[4], 再 + 1, 就把 int[4] 转成 int* 了
	printf("%d\n", sizeof(arr[0] + 1)); // 4 sizeof(int*)
	printf("%d\n", sizeof(&arr[0] + 1)); // 4 sizeof(int(*)[4])
	printf("%d\n", sizeof(arr[0][0])); // 4 sizeof(int)
	printf("%d\n", sizeof(*arr[0])); // 4, sizeof(int)

这里重点讨论arr[0] + 1和*arr[0]。

需要注意的是,在二维数组的情况下,arr[0]的类型是int[4]int[4]本身就是一个数组名。可以这么理解:

	int a[4];

那么 a 的类型就是 int[4]
因此,arr[0] + 1,由于有 + 1操作,int[4]转为int*,也可以理解为 一个整形一维数组名+1(即a+1),转为了int*。

而*arr[0],则是int[4]解引用(可以看作*a),得到首元素,即int类型。

有了以上的认识,大部分问题就迎刃而解了。我们继续看代码:

	// arr 是二维数组 int[3][4] => int(*)[4] => * => int[4]
	/printf("%d\n", sizeof(*arr)); // 16 sizeof(int[4])
	// arr + 1 => int(*)[4] => * => int[4]
	printf("%d\n", sizeof(*(arr + 1))); // 16 sizeof(int[4])
	// arr => int[3][4] => int(*)[4] => * => int[4] => +1 => int*
	printf("%d\n", sizeof(*arr + 1));
	// arr[0] => int[4] => & => int(*)[4] => +1 => int(*)[4]
	printf("%d\n", sizeof(&arr[0] + 1)); // 4 sizeof(int(*)[4])
	// arr[0] => int[4] => +1  => int*  => * => int
	printf("%d\n", sizeof(*(arr[0] + 1)));

注意*arr+1,arr是int[3][4],隐式转换成int(*)[4],解引用得到int[4],由于有 + 1操作,由上文可知,得到int*类型,所以输出4。

*(arr[0] + 1),int[4]=>int*=>int,输出4。

再来观察一个有趣的代码:

	printf("%d\n", sizeof(arr[100]));

由于我们定义的arr是3*4的,那么这里会输出什么?是否会直接报错?答案是否定的。

由于sizeof 是编译期求值, 这个代码不是未定义行为。编译期过程中不涉及内存访问, 更没有数组下标越界。代码编译完了之后生成的二进制指令相当于

	printf("%d\n", 16);

因此这里计算出的值16,和arr[0]没有任何区别。

PS:在C++ 中,编译期求值的相关内容有sizeof, const, template, constexpr。

四、另一些例子

笔试题1
	int a[5] = { 1, 2, 3, 4, 5 };
	int *ptr = (int *)(&a + 1);
	// *(a + 1) => a[1]
	printf("%d,%d", *(a + 1), *(ptr - 1));

打印的第一个值不难求出,首元素地址+1再解引用,得到第二个元素的值,为2。

而(int *)(&a + 1),则是由int*,转为了int(*)[5],即数组指针,再 + 1,相当于跳过了整个数组,此时指向元素5后面一个字节。强转为int*则是控制 + 1或 - 1时指针移动的幅度。

所以ptr - 1时,向前移动4个字节,指向元素5,再解引用,就得到了5。

笔试题2
struct Test
{
	int Num;
	char *pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;

假设p 的值为0x100000。 如下表表达式的值分别为多少?
p + 0x1 = 0x___ ?
答:相当于+1,得到0x100001

(unsigned long)p + 0x1 = 0x___ ?
答:强转之后成为100000,+1,得到100001

(unsigned int*)p + 0x1 = 0x___ ?
答:int*类型+1,向后跳4个字节,得到0x100004

笔试题3
	int a[4] = { 1, 2, 3, 4 };
	int *ptr1 = (int *)(&a + 1);
	int *ptr2 = (int *)((int)a + 1);
	printf("%x,%x", ptr1[-1], *ptr2);

ptr1[-1]的求法与笔试题1相同。
对于ptr2来说,(int)a+1表示数组首元素地址转为int类型整数,再+1,相当于向高位偏移了一个字节。再强转为(int*),控制读取数据的长度。

因此*ptr2应该是偏移一个字节之后,再读接下来四个字节的数据,所得的数值。以小端字节序为例,如下图所示:
在这里插入图片描述

笔试题4
	// 由于后面的初始化语句都是 逗号表达式, 
	// 实际有效的值只是 1, 3, 5
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int *p;
	p = a[0];
	printf("%d", p[0]);

初始化之后数组内的值应该为{{ 1 , 3 } , { 5 , 0 } , { 0 , 0 }},p[0]相当于是a[0][0],因此输出1。


总结

可以发现,解题时没有放之四海皆准的办法,只能具体情况具体分析,结合数据类型、指针、数组和内存的知识,按照运算符的优先级逐步分析,求得数据的值和类型。

<think>我们正在讨论C/C++中的sizeofstrlen的区别及用法。根据引用内容,我们可以总结以下几点:1.sizeof是运算符,strlen是C语言标准库函数(在C++中也可用,但需包含<cstring>)。2.strlen测量字符串的实际长度,以'\0'结束,返回结果不包括'\0'。3.sizeof测量的是对象或类型所占内存的大小(单位字节),其参数可以是数组、指针、类型、对象、函数等。具体区别:-对于字符串字面量或字符数组:-例如:chars[100]="0123456789";-sizeof(s)返回100,因为这是数组在内存中的总大小(100个char,每个1字节)。-strlen(s)返回10,因为这是从开始到第一个'\0'的字符数(不包括'\0')。-对于指针:-例如:char*b="helloworld";-sizeof(b)返回指针的大小(在32位系统中通常为4字节,64位系统中为8字节)。-sizeof(*b)返回char类型的大小,即1。-而strlen(b)则返回字符串的长度10。另外,sizeof在编译时计算(除了C99中的变长数组),而strlen在运行时计算。我们还可以通过例子来说明:示例1:字符数组charstr[]="Hello";sizeof(str)=6,因为数组包括6个字符(包括结尾的'\0')所以是6字节。strlen(str)=5,因为计算的是5个非'\0'的字符。示例2:指针指向字符串char*str="Hello";sizeof(str)=指针的大小(4或8字节)sizeof(*str)=1(因为*str是第一个字符'H',占1字节)strlen(str)=5示例3:自定义类型typedefstruct{inta;charb;doublec;}MyStruct;sizeof(MyStruct)将根据结构体的内存对齐规则来计算,可能不是简单相加(例如在32位系统中可能是16字节,因为double通常8字节对齐,所以int(4)+char(1)+填充3字节+double(8)=16)。示例4:函数参数中的数组退化为指针voidfunc(chararr[100]){sizeof(arr);//这里arr是指针,所以大小是指针大小(4或8),而不是100}因此,在使用时要注意:-当需要知道字符串的长度(字符个数)时,使用strlen。-当需要知道数组、结构体、基本类型等所占内存大小时,使用sizeof。同时,注意sizeof对于数组名指针的不同行为:数组名在sizeof中返回整个数组的大小,而指针返回指针本身的大小。下面我们通过代码示例来进一步说明。</think>### sizeof strlen 的区别及用法详解 在 C/C++ 中,`sizeof` `strlen` 是两个易混淆的概念,主要区别如下: #### 1. **本质区别** - **`sizeof`**:编译器运算符,**编译时**确定结果,用于计算对象或类型的内存占用大小(单位:字节) - **`strlen`**:标准库函数(需包含 `<cstring>`),**运行时**计算,用于获取字符串的实际长度(不含终止符 `\0`) #### 2. **行为差异** ```c++ char s[100] = "0123456789"; printf("%zu\n", sizeof(s)); // 输出 100(整个数组大小) printf("%zu\n", strlen(s)); // 输出 10(有效字符数) char* ptr = "hello"; printf("%zu\n", sizeof(ptr)); // 输出 4 或 8(指针大小) printf("%zu\n", sizeof(*ptr)); // 输出 1(char 类型大小) printf("%zu\n", strlen(ptr)); // 输出 5(字符串长度) ``` #### 3. **关键特性** | 特性 | sizeof | strlen | |--------------|----------------------------|----------------------------| | 参数类型 | 类型/对象/数组/指针等 | 必须以 `\0` 结尾的字符串 | | 处理 `\0` | 计入大小 | 停止计数标志 | | 空字符串 | `sizeof("") = 1` | `strlen("") = 0` | | 未初始化指针 | 返回指针大小 | 未定义行为(可能崩溃) | #### 4. **典型应用场景** - **`sizeof` 适用**: ```c++ int arr[10]; size_t arr_size = sizeof(arr); // 计算数组总字节数 struct Data { int id; char name[20]; }; size_t struct_size = sizeof(Data); // 结构体内存对齐大小 ``` - **`strlen` 适用**: ```c++ char input[100]; fgets(input, sizeof(input), stdin); size_t len = strlen(input); // 获取实际输入长度 if (strcmp(input, "quit") == 0) // 结合字符串比较[^2] exit(0); ``` #### 5. **常见误区** ```c++ char str[5] = {'a','b','c'}; // sizeof(str)=5 ✅ 但 strlen(str) 可能>3 ❌(缺少终止符) ``` > **重要提示**:`strlen` 要求字符串必须以 `\0` 结尾,否则会导致未定义行为[^5]。在操作字符数组时,建议显式初始化终止符。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值