数组和指针是常用的数据类型,了解它们的sizeof和strlen的使用是编写高效程序的关键。本文将通过一系列示例代码详细解释数组和指针在不同场景下的sizeof和strlen的计算方式。
一维数组
首先,我们来看一维数组的情况:
int main() {
int a[] = {1, 2, 3, 4};
printf("%d\n", sizeof(a)); // 16
printf("%d\n", sizeof(a + 0)); // 4
printf("%d\n", sizeof(*a)); // 4
printf("%d\n", sizeof(a + 1)); // 4
printf("%d\n", sizeof(a[1])); // 4
printf("%d\n", sizeof(&a)); // 4
printf("%d\n", sizeof(&a + 1)); // 4
printf("%d\n", sizeof(&a[0] + 1)); // 4
return 0;
}
在这个例子中,a
是一个包含4个整数的数组。下面是对上述代码各行的详细解释:
sizeof(a)
:返回整个数组所占的字节数,这里是4个整数 * 4字节/整数 = 16字节。sizeof(a + 0)
:数组名加0等于数组本身,但在这里被解释为数组指针,所以返回指针大小,通常是4字节。sizeof(*a)
:返回数组的第一个元素的大小,这里是4字节,因为a
是int类型数组。sizeof(a + 1)
:数组名加1表示指向数组下一个元素的指针,所以返回指针大小,通常是4字节。sizeof(a[1])
:返回数组的第二个元素的大小,同样是4字节,因为a[1]
是int类型。sizeof(&a)
:返回数组的地址的大小,通常是4字节。sizeof(&a + 1)
:数组地址加1,跳过整个数组,返回地址大小,通常是4字节。sizeof(&a[0] + 1)
:数组第一个元素的地址加1,同样返回地址大小,通常是4字节。
这里需要注意的是,sizeof(a)
计算的是整个数组的大小,而sizeof(&a)
计算的是数组的地址的大小。
字符数组
接下来,我们看看字符数组的情况:
int main() {
char arr[] = {'a', 'b', 'c', 'd', 'e', 'f'};
printf("%d\n", sizeof(arr)); // 6
printf("%d\n", sizeof(arr + 0)); // 4
printf("%d\n", sizeof(*arr)); // 1
printf("%d\n", sizeof(arr[1])); // 1
printf("%d\n", sizeof(&arr)); // 4
printf("%d\n", sizeof(&arr + 1)); // 4
printf("%d\n", sizeof(&arr[0] + 1));// 4
return 0;
}
在这个例子中,arr
是一个包含6个字符的数组。下面是对上述代码各行的详细解释:
sizeof(arr)
:返回整个字符数组所占的字节数,这里是6个字符 * 1字节/字符 = 6字节。sizeof(arr + 0)
:数组名加0等于数组本身,但在这里被解释为数组指针,所以返回指针大小,通常是4字节。sizeof(*arr)
:返回字符数组的第一个元素的大小,这里是1字节,因为arr
是char类型数组。sizeof(arr[1])
:返回字符数组的第二个元素的大小,同样是1字节,因为arr[1]
是char类型。sizeof(&arr)
:返回字符数组的地址的大小,通常是4字节。sizeof(&arr + 1)
:数组地址加1,跳过整个数组,返回地址大小,通常是4字节。sizeof(&arr[0] + 1)
:数组第一个元素的地址加1,同样返回地址大小,通常是4字节。
这里需要注意的是,sizeof(arr)
计算的是整个字符数组的大小,而sizeof(&arr)
计算的是数组的地址的大小。
字符串数组
接下来,我们看看字符串数组的情况:
int main() {
char arr[] = "abcd";
printf("%d\n", strlen(arr)); // 4
printf("%d\n", strlen(arr + 0)); // 4
// 下面两行是非法的,因为strlen函数需要一个指向字符串的指针作为参数
// printf("%d\n", strlen(*arr));
// printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr)); // 随机值,传递整个地址给strlen
printf("%d\n", strlen(&arr + 1)); // 随机值,跳到下一个数组
printf("%d\n", strlen(&arr[0] + 1)); // 随机值,第一个元素跳到第二个元素
return 0;
}
在这个例子中,arr
是一个包含4个字符的字符串数组。下面是对上述代码各行的详细解释:
strlen(arr)
:返回字符串 "abcd" 的长度,结果为4。strlen(arr + 0)
:arr + 0
等于arr
,因为它们都指向数组的起始位置,所以结果也为4。strlen(*arr)
:非法。strlen函数需要一个指向字符串的指针作为参数,而*arr
是一个字符,所以传递*arr
给 strlen 是错误的。strlen(arr[1])
:非法。arr[1]
是一个字符,不能作为 `strlen‘的参数strlen(&arr)
:随机值。整个地址传给strlen
,这可能会偶然在内存中找到一个\0
字节,从而返回一个非预期的长度值。strlen(&arr + 1)
:随机值。跳到下一个数组,同样传递整个地址给strlen
。strlen(&arr[0] + 1)
:随机值。第一个元素的地址加1,相当于跳到第二个元素,比&arr
少一个元素。- 需要注意的是,
strlen
函数需要一个指向字符串的指针作为参数,而&arr
、&arr + 1
、&arr[0] + 1
在这里都是地址,而非指向字符串的指针。
字符指针
现在,我们来看字符指针的情况:
int main() {
char* p = "abcde";
printf("%d\n", sizeof(p)); // 随机值,通常为 4 或 8
printf("%d\n", sizeof(p + 0)); // 随机值,通常为 4 或 8
printf("%d\n", sizeof(*p)); // 1
printf("%d\n", sizeof(p[0])); // 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)); // 随机值,可能会找到 \0 字节返回一个非预期的长度值
printf("%d\n", strlen(p + 0)); // 随机值,同上
// 下面两行是非法的,因为strlen函数需要一个指向字符串的指针作为参数
// printf("%d\n", strlen(*p));
// printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p)); // 随机值,整个地址传给strlen
printf("%d\n", strlen(&p + 1)); // 随机值,跳到下一个地址
printf("%d\n", strlen(&p[0] + 1)); // 随机值,第一个元素的地址加1,相当于跳到第二个元素
return 0;
}
在这个例子中,p
是一个字符指针,指向字符串 "abcde" 的起始位置。下面是对上述代码各行的详细解释:
sizeof(p)
:返回指针的大小,通常为 4 或 8 字节,具体取决于系统位数。sizeof(p + 0)
:指针加0等于指针本身,返回指针大小。sizeof(*p)
:返回字符的大小,这里是1字节。sizeof(p[0])
:同样返回字符的大小,1字节。sizeof(&p)
:随机值返回指针的大小sizeof(&p + 1)
:随机值指针地址加1,跳到下一个地址,返回地址大小。sizeof(&p[0] + 1)
:第一个元素的地址加1,相当于跳到第二个元素,返回地址大小。
strlen
函数的使用情况与前面的例子相似,需要注意传递给 strlen
的参数必须是指向字符串的指针。
二维数组
最后,我们来看二维数组的情况:
int main() {
int p[3][4] = {0};
printf("%d\n", sizeof(p)); // 48
printf("%d\n", sizeof(p[0][0])); // 4
printf("%d\n", sizeof(p[0])); // 16
printf("%d\n", sizeof(p[0] + 1)); // 4
printf("%d\n", sizeof(*(p[0] + 1)));// 4
printf("%d\n", sizeof(p + 1)); // 4
printf("%d\n", sizeof(*(p + 1))); // 16
printf("%d\n", sizeof(&p[0] + 1)); // 4
printf("%d\n", sizeof(*(&p[0] + 1)));// 16
printf("%d\n", sizeof(*p)); // 16
printf("%d\n", sizeof(p[3])); // 16
printf("---------------------\n");
// 验证
printf("%p ", &p[0][0]);
printf("%p ", &p[0][1]);
printf("%p ", p[0] + 1);
return 0;
}
在这个例子中,p
是一个包含3行4列的二维数组。下面是对上述代码各行的详细解释:
sizeof(p)
:返回整个二维数组所占的字节数,这里是3 * 4 * 4 = 48字节。sizeof(p[0][0])
:返回二维数组中的单个元素的大小,这里是4字节。sizeof(p[0])
:返回二维数组的第一行的大小,相当于数组名,所以返回的是第一行的大小,16字节。sizeof(p[0] + 1)
:第一行的地址加1,相当于跳到第二个元素,返回地址大小,4字节。sizeof(*(p[0] + 1))
:第一行的地址加1,解引用,相当于取第二个元素的大小,4字节。sizeof(p + 1)
:p
是二维数组的数组名,这里返回的是第二行的地址大小,4字节。sizeof(*(p + 1))
:第二行的地址解引用,相当于取整个第二行的大小,16字节。sizeof(&p[0] + 1)
:第一行的地址加1,相当于跳到第二行的地址,返回地址大小,4字节。sizeof(*(&p[0] + 1))
:第一行的地址加1,解引用,相当于取整个第二行的大小,16字节。sizeof(*p)
:p
是二维数组的数组名,返回的是第一行的大小,16字节。sizeof(p[3])
:其实sizeof并不会真正去访问,只是会通过变量来推导类型,所以并不会真的去访问它。在这里a[3]其实是数组指针类型,int (*)[3],大小为4或8个字节。
接下来,通过验证部分,我们输出一些地址以验证我们的计算:
// 验证
printf("%p ", &p[0][0]);
printf("%p ", &p[0][1]);
printf("%p ", p[0] + 1);
这部分代码输出了一些地址,其中 &p[0][0]
是第一个元素的地址,&p[0][1]
是第一个元素的地址加1,而 p[0] + 1
是第一行的地址加1。这些输出的地址应该有助于理解地址的增长。
在这个例子中,p
被看作是一个包含3行4列的二维数组。需要注意的是,在计算二维数组大小时,sizeof(p)
计算的是整个数组的大小,而 sizeof(p[0])
计算的是数组的第一行的大小,相当于数组名。此外,sizeof(p[0][0])
计算的是数组中单个元素的大小。
通过这一系列的例子,我们深入了解了C语言中数组和指针在不同场景下的 sizeof
和 strlen
的计算方式,对于理解内存布局和地址的增长是非常有帮助的。希望这篇博客能够对C语言初学者和进阶者有所启发。
总结:
- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这里的数组名表示整个数组,去除的是整个数组的地址。
- 除此之外所有的数组名都表示首元素的地址。