本期是C语言基础中有关指针的最后一篇博客,本期将为大家介绍sizeof和strlen的对比、数组和指针乃至指针运算的笔试题解析。那么现在让我们开始吧!
在学习操作符的时候,我们学习了sizeof,sizeof是用来计算变量所占内存空间的大小的,单位是字节,如果操作数是类型的话,计算的就是适用类型创建的变量所占内存空间的大小。
sizeof只关注占用内存空间的大小,不在乎内存中存放了什么数据。
比如:
#inculde <stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a);
printf("%d\n", sizeof(int));
return 0;
}strlen是C语言中的一个库函数,其功能是求字符串长度。函数原型如下:
size_t strlen ( const char * str );该函数统计的是从strlen函数的参数str这个指针指向的地址开始向后,一直到 '\0' 之前的字符串中字符的个数。
strlen函数会一直向后找 '\0' 字符,直到找到为止,所以可能存在越界查找的情况。
strlen函数的演示如下所示:
#include <stdio.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;
}sizeof | strlen |
|---|---|
1. sizeof是操作符 | 1. strlen是库函数,使用需要包含头文件string.h |
2. sizeof计算操作数所占内存的大小,单位是字节 | 2. strlen是求字符串长度的,统计的是 \0 之前字符的个数 |
3. 不关注内存中存放什么数据 | 3. 关注内存中是否有 \0,如果没有 \0,就会持续向后找,可能会越界。 |
首先,我们来回顾一下数组名有何意义:
我们来通过下面这些例题,学会判断sizeof函数内的表示的是整个数组还是首元素地址:
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));分析如下:

同样让我们来先看一些例题:
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));例题一分析如下:

char arr[] = {'a','b','c','d','e','f'};
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));例题二分析如下:

分析输出结果。
char arr[] = "abcdef";
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));例题三分析如下:

分析代码结果。
char arr[] = "abcdef";
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));例题四分析如下:

分析输出结果:
char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));例题五分析结果如下:

分析输出结果:
char *p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));例题六分析结果如下:

分析下列代码的输出结果:
int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));分析结果如下:

#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的基础上跳过一个元素,即指向数组中的第二个元素,故*(a+1)的值为数组中的第二个元素2;由于&a表示整个数组的地址,故&a+1就表示跳过整个数组,所以*ptr就是a数组最后一个元素之后的地址,故ptr-1指向的就是a数组的最后一个元素,因此*(ptr-1)的值为5。输出结果如下图所示:

//在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;
}在计算机系统中,对齐(Alignment) 是指数据在内存中的存储位置必须满足特定边界要求,以提高访问效率。不同的数据类型有不同的对齐规则,这会影响结构体的大小和内存布局。
结构体的对齐方式受 最严格成员的对齐要求 影响,并可能插入 填充字节(Padding) 来满足对齐。规则总结如下:
int(4)、char(1)、double(8),则结构体按 8 字节对齐。int(4 字节对齐)放在偏移 2,编译器会插入 2 字节填充,使其移动到 4。sizeof(struct) 是最大对齐值的整数倍。在x86环境下,默认是4字节对齐(32位系统),此时结构体Test的成员大小和对齐情况如下:

因此Test的总大小=4(Num)+ 2(sDate)+ 2(cha)+ 8(sBa)= 20字节
p是一个struct Test* 指针,其初始值为0x100000。在C语言中,指针运算的单位是指向类型的大小。
由于p的类型是struct Test*,且sizeof(struct Test)= 20,因此p+0x1相当于p + 1,即向后移动1个struct Test的大小,计算过程如下所示:
0x100000 + 1 * 20 = 0x100000 + 0x14 = 0x100014最后输出的值为:0x100014
(unsigned long)p将指针p强制转换为了unsigned int*(指向unsigned int 的指针)。我们又可以知道,在x86环境下,sizeof(unsigned int)的值为4,所以 (unsigned int*)p + 0x1就相当于向后移动1个unsigned int 的大小,计算如下:
0x100000 + 1 * 4 = 0x100004最终输出的值为:0x100001
这一步的计算与上一步同理,最后可得计算过程如下:
0x100000 + 1 * 4 = 0x100004最后的输出结果为:0x100004
因此,最后整个代码的输出结果为:
0x100014 // p + 0x1(结构体指针运算)
0x100001 // (unsigned long)p + 0x1(纯数值运算)
0x100004 // (unsigned int*)p + 0x1(整型指针运算)#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;
}
//输出结果是什么?上述代码有什么问题?首先,代码中给二维数组a赋值的部分存在错误,像(0,1)这样的表达式并不是数组初始化的语法,而是逗号表达式。在C语言中,逗号表达式(x,y)的值是y,即最后一个表达式的值;因此(0,1)的值是1,(2,3)的值是3,(4,5)的值是5.
受逗号表达式的影响,数组a的实际初始化方式相当于:
int a[3][2] = { 1, 3, 5 }; // 其余部分默认补 0即:
a[0][0] = 1
a[0][1] = 3
a[1][0] = 5
a[1][1] = 0
a[2][0] = 0
a[2][1] = 0再来看指针p的赋值部分,a[0]是二维数组a的第一行,其类型为int[2],但会退化为 int*。而p指向了a[0][0],即 p = &a[0][0]。又由于我们已知a[0][0] = 1,因此p[0]的值就是1。最后程序输出的结果就是1。
正确的初始化方式如下:
int a[3][2] = { {0, 1}, {2, 3}, {4, 5} }; // 正确写法即应该将[ ] 替换为{ },此时程序输出的结果为0。
#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是一个 2x5 的二维数组,初始化如下:
int aa[2][5] = {
{1, 2, 3, 4, 5}, // aa[0]
{6, 7, 8, 9, 10} // aa[1]
};内存布局如下(假设 int 占 4 字节):
地址 值
&aa[0][0] 1
&aa[0][1] 2
&aa[0][2] 3
&aa[0][3] 4
&aa[0][4] 5
&aa[1][0] 6
&aa[1][1] 7
&aa[1][2] 8
&aa[1][3] 9
&aa[1][4] 10 对于ptr1, &aa 是 整个二维数组的地址,类型是 int(*)[2][5](指向 2x5 数组的指针)。&aa + 1 会跳过整个aa,即 + sizeof(aa) = +40字节(2x5x4)。强制转换为 int* 后,ptr1 指向 aa 的末尾(即 &aa[1][4] + 1)。ptr1 指向 aa 的末尾,ptr1 - 1回退 1 个 int (4 字节)。所以 *(ptr1 - 1)的值 就是 aa[1][4],即 10。
aa 是二维数组名,类型是 int [2][5],但会退化为 int (*)[5](指向第一行的指针)。aa + 1 指向第二行 aa[1],即 &aa[1]。*(aa + 1) 解引用得到 aa[1],类型是 int[5],但退化为 int*(指向 aa[1][0])。所以 ptr2 指向 aa[1][0](即 6)。ptr2 指向 aa[1][0],ptr2 - 1 回退 1 个 int(4 字节)。所以 *(ptr2 - 1) 就是 aa[0][4],即 5。
因此本题最后的输出结果为:10,5
C语言指针部分的内容到本期就正式结束啦!相信读完我的文章的大家应该能有所收获,那么也还请继续关注博主,C语言的学习之路还没有结束哦~~~