欢迎光临
我们一直在努力

C语言指针精讲(六):sizeof与strlen核心对比、数组指针经典笔试题解析、指针运算高频笔试题解析

1.sizeof与strlen的对比
2.数组和指针经典笔试题解析
3.指针运算笔试题解析

1.sizeof与strlen的对比
1.1sizeof

sizeof 计算变量所占内存空间大小的,单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。sizeof 只关注占用内存空间的大小,不在乎内存中存放什么数据。

int a = 10;
printf("%d\\n", sizeof(a)); // 输出 4(int 占4字节)
printf("%d\\n", sizeofa); // 同上,还是 4
printf("%d\\n", sizeof(int)); // 输出 4

!!!类型名:必须带括号,例如:  sizeof(int) 
!!!变量/表达式:括号可省略,写成  sizeof a , sizeof  和变量名之间必须有空格

1.2strlen

strlen 是C语言库函数,功能是求字符串长度。函数原型如下:

size_t strlen ( const char * str );

统计的是从 strlen 函数的参数 str 中这个地址开始向后, ‘\\0’ 之前字符串中字符的个数。

strlen 函数会⼀直向后找‘ \\0’ 字符,直到找到为止,所以可能存在越界查找

char arr1[3] = {'a', 'b', 'c'}; // 没有 '\\0'
char arr2[] = "abc"; // 自动带 '\\0',实际是 {'a','b','c','\\0'}

printf("%d\\n", strlen(arr1)); // 结果不确定!会越界,输出随机值
printf("%d\\n", strlen(arr2)); // 输出 3('a'/'b'/'c',不含 '\\0')

printf("%d\\n", sizeof(arr1)); // 输出 3(数组大小是3字节)
printf("%d\\n", sizeof(arr2)); // 输出 4('a'/'b'/'c'/'\\0' 共4字节)

arr1 因为没有 \\0,strlen 会一直往后找,可能会输出很大的数,甚至程序崩溃。
arr2 因为字符串常量自带 \\0,所以 strlen 正确算出 3,sizeof 算上 \\0 是 4。

1.3 sizeof 和 strlen的对比
sizeof strlen
1. sizeof是操作符 1. strlen是库函数,使⽤需要包含头⽂件 string.h
2. sizeof计算操作数所占内存的大小,单位是字节 2. srtlen是求字符串⻓度的,统计的是 \\0 之前字符的个数
3. 不关注内存中存放什么数据 3. 关注内存中是否有 \\0 ,如果没有 \\0 ,就会持续往后找,可能 会越界

1.4高频易错点

sizeof 对数组名的特殊处理

数组名作为 sizeof 的参数时,代表整个数组,所以 sizeof(arr) 是总字节数。

但如果数组名传给函数,函数里的形参退化成指针,sizeof(arr) 就变成指针的大小(32 位系统 4 字节,64 位系统 8 字节)。

strlen 必须遇到 \\0 才停止
只有用双引号初始化的字符串,才会自动加上 \\0。
用 {'a','b','c'} 这种方式初始化的数组,不会自动加 \\0,用 strlen 会出问题。

sizeof 不会计算 \\0 吗?
不是!sizeof 会把数组里的所有字节都算进去,包括 \\0。比如 "abc" 作为数组初始化时,sizeof 会把末尾的 \\0 算进去,所以结果是 4。

一句话总结sizeof 看的是 “占多大内存”,不管内容。

strlen 看的是 “有效字符串有多长”,必须靠 \\0 终止。

2. 数组和指针笔试题解析

数组名的意义:

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。

2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

3. 除此之外所有的数组名都表示首元素的地址。

2.1 一维数组

int a[] = {1,2,3,4};
printf("%d\\n",sizeof(a));//16,,sizeof(数组名)代表数组总字节大小
printf("%d\\n",sizeof(a+0));//4/8 a+0 是首元素地址(指针),32位系统4字节,64位8字节
printf("%d\\n",sizeof(*a));//4 相当与a[0]
printf("%d\\n",sizeof(a+1));//4/8 相当于数组第二位地址
printf("%d\\n",sizeof(a[1]));//4 数组第二个int元素
printf("%d\\n",sizeof(&a));//4/8 相当于整个数组的地址
printf("%d\\n",sizeof(*&a));//16 *对整个数组进行解引用,得到整个数组
printf("%d\\n",sizeof(&a+1));//4/8 +1相当于跳过整个数组,指向数组后面的地址
printf("%d\\n",sizeof(&a[0]));4/8 数组首元素地址
printf("%d\\n",sizeof(&a[0]+1));4/8 数组第二位地址

2.2 字符数组

代码1:

#include <stdio.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};//一个char元素代表一个字节
printf("%d\\n", sizeof(arr));//6 整个数组大小
printf("%d\\n", sizeof(arr+0));//4/8 数组首元素地址
printf("%d\\n", sizeof(*arr));//1 相当于arr[0]
printf("%d\\n", sizeof(arr[1]));//1 一个char元素代表一个字节
printf("%d\\n", sizeof(&arr));//4/8 整个数组的地址
printf("%d\\n", sizeof(&arr+1));//4/8 跳过整个数组,指向下一个首地址
printf("%d\\n", sizeof(&arr[0]+1));//4/8 相当于第二位的地址
return 0;
}

代码2:

代码2:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d\\n", strlen(arr));//随机值 数组中没有\\0,会一直从首元素开始寻找
printf("%d\\n", strlen(arr+0));//随机值 从首元素地址开始越界访问
printf("%d\\n", strlen(*arr));//程序崩溃 *arr是 'a'(ASCII值97)传给strlen会被当成地址,非法访问
printf("%d\\n", strlen(arr[1]));//程序崩溃 arr[1]是'b'(ASCII值98),同样非法访问
printf("%d\\n", strlen(&arr));//随机值 同样越界访问
printf("%d\\n", strlen(&arr+1));//随机值 指向数组后面的地址,越界访问
printf("%d\\n", strlen(&arr[0]+1));//从第二个数组开始向后寻找\\0,越界访问
return 0;
}

代码3:

#include <stdio.h>
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 一个char元素,占一个字节
printf("%d\\n", sizeof(arr[1]));//1 数组第二个char元素
printf("%d\\n", sizeof(&arr));//4/8 整个数组地址
printf("%d\\n", sizeof(&arr+1));//4/8 跳过这个数组,指向下一个数组首元素
printf("%d\\n", sizeof(&arr[0]+1));//4/8 第二个数组地址
return 0;
}

代码4:

#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
printf("%d\\n", strlen(arr));//6 从首地址到\\0之前,共6个字节
printf("%d\\n", strlen(arr+0));//6 和strlen(arr)一样,从首地址到\\0之前
printf("%d\\n", strlen(*arr));//报错 *arr 是 'a'(ASCII 值 97),非法地址
printf("%d\\n", strlen(arr[1]));//报错 arr[1] 是 'b'(ASCII 值 98),非法地址
printf("%d\\n", strlen(&arr));//6 从首地址到\\0之前,共6个字节
printf("%d\\n", strlen(&arr+1));//报错 跳过整个数组。越界访问
printf("%d\\n", strlen(&arr[0]+1));//5 从b开始,到\\0之前结束
return 0;
}

代码5:

#include <stdio.h>
int main()
{
char *p = "abcdef";
printf("%d\\n", sizeof(p));//4/8 char *指针
printf("%d\\n", sizeof(p+1));//4/8 第二个 char *指针
printf("%d\\n", sizeof(*p));//1 a是一个char元素
printf("%d\\n", sizeof(p[0]));//1 p[0]相当于*p,
printf("%d\\n", sizeof(&p));//4/8 char *指针
printf("%d\\n", sizeof(&p+1));//4/8 跳过整个数组,指向下一个数组首地址,本质是指针
printf("%d\\n", sizeof(&p[0]+1));//4/8 第二个 char *指针
return 0;
}

代码6:

#include <stdio.h>
#include <string.h>
int main()
{
char *p = "abcdef";
printf("%d\\n", strlen(p));//6 从p指向的地址开始找\\0,共6个字符
printf("%d\\n", strlen(p+1));//5 从第二个字符指向的地址开始找\\0,共5个字符
printf("%d\\n", strlen(*p));//报错 *p是'a'(ASCII 值 97)非法地址
printf("%d\\n", strlen(p[0]));//报错 跟*p相同,非法地址
printf("%d\\n", strlen(&p));//随机值 &p是指针变量的地址,指向的是栈上的内容,不是字符串,会越界
printf("%d\\n", strlen(&p+1));//随机值 指向指针变量后面的地址,越界访问
printf("%d\\n", strlen(&p[0]+1));//5 跟strlen(p+1)一样
return 0;
}

2.3 二维数组

#include <stdio.h>
int main()
{
int a[3][4] = {0};
printf("%d\\n",sizeof(a));//48 整个二维数组总大小
printf("%d\\n",sizeof(a[0][0]));//4 二维数组第一个int元素
printf("%d\\n",sizeof(a[0]));//16 第一行数组总大小
printf("%d\\n",sizeof(a[0]+1));//4/8 第一行第二个数据地址
printf("%d\\n",sizeof(*(a[0]+1)));//4 解引用得到第一行第二个int数据
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 a[3]是第4行的数组名(虽然越界,但sizeof只看类型,不访问内存),一行大小 16 字节
return 0;
}

3. 指针运算笔试题解析
3.1 题目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;
}
//程序的结果是什么?

解析:

1.  int a[5]  整体数组类型  int[5] ; &a  是数组指针 int(*)[5],+1 直接跳过整个数组5个int
2.  ptr=(int*)(&a+1) :指向数组末尾下一个int地址

3.  *(a+1)  =  a[1]  → 2
4.  ptr-1  回退1个int,指向数组最后元素  a[4]  → 5

输出结果:2 5

3.2 题目2

//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
struct Test
{
int Num; //4字节
char *pcName; //4字节(X86环境下为4字节)
short sDate; //2字节
char cha[2]; //2字节
short sBa[4]; //8字节
}*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;
}

解析:

1.p 是 struct Test* 类型,p+1 会跳过整个结构体大小(20 字节),所以 p+0x1 = 0x100000 + 20 = 0x100014。

2.(unsigned long)p:把指针转为无符号长整型,此时 +0x1 就是简单的数值加 1,结果为 0x100001。

3.(unsigned int*)p:把指针转为 unsigned int* 类型,+1 会跳过 sizeof(unsigned int)=4 字节,所以结果为 0x100000 + 4 = 0x100004。

运行结果

0x100014
0x100001
0x100004

3.3 题目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)

它的作用是取最后一个值:

(0, 1) → 结果为 1

(2, 3) → 结果为 3

(4, 5) → 结果为 5

因此初始化为

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[0] 是二维数组第一行的数组名,指向 a[0][0] 的 int* 指针,所以 p 指向 a[0][0]。p[0] 等价于 *(p + 0),解引用得到 a[0][0] 的值。

输出结果为:1

3.4 题目4

//假设环境是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;
}

解析:

int a[5][5]:二维数组,每行 5 个int,共 5 行。

int (*p)[4]:数组指针,指向一个包含 4 个int的一维数组,步长为 4*4=16 字节。

p = a:a 退化为指向第一行的指针(类型为 int(*)[5]),赋值给 p 后,p 按步长 16 字节偏移。

&a[4][2]:指向第 4 行第 2 列元素,是数组中第 4*5 + 2 = 22 个int元素。

&p[4][2]:p[4] 等价于 *(p + 4),p 步长为 4 个int,所以 p+4 跳过 4*4=16 个int,指向第 16 个int的位置。p[4][2] 等价于 *(p[4] + 2),即第 16 + 2 = 18 个int元素。

两个地址的差:18 – 22 = -4(单位是int,即相差 – 4 个元素)。

输出分析

%p 会把 -4 当成无符号地址输出,-4的补码是1111 1111 1111 1111 1111 1111 1111 1100 内存中存的是补码 当成无符号地址打印

结果是 0xfffffffc(x86 环境下)。

%d 直接输出差的数值 -4。

输出结果:0xfffffffc 4

3.5 题目5

#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:表示整个二维数组的地址,类型为 int(*)[2][5]。

&aa + 1:跳过整个数组(共 2*5=10 个int,40 字节),指向数组末尾的下一个地址。

ptr1 = (int *)(&aa + 1):强转为int*,指向数组末尾的下一个位置。

ptr1 – 1:回退 1 个int,指向数组最后一个元素 10,*(ptr1 – 1) = 10。

aa:二维数组名,退化为指向第一行的指针(类型为 int(*)[5])。

aa + 1:跳过一行(5 个int,20 字节),指向第二行的起始地址。

*(aa + 1):等价于 aa[1],退化为指向第二行第一个元素的int*指针(值为&aa[1][0])。

ptr2 = (int *)(*(aa + 1)):指向第二行第一个元素 6。

ptr2 – 1:回退 1 个int,指向第一行最后一个元素 5,*(ptr2 – 1) = 5。

输出结果: 10 5

3.6 题目6

#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\\n", *pa);
return 0;
}

解析:

a 是 char* 数组,存放了 3 个字符串的地址:a[0] = "work", a[1] = "at", a[2] = "alibaba"。

pa 是 char**,指向数组 a 的首元素地址(即 &a[0])。

pa++:指针 + 1,跳过一个 char* 大小,指向 &a[1]。

*pa:解引用得到 a[1],即字符串 "at" 的地址。

输出结果是 at

3.7题目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;
}

先把顺序排好

c数组:
c[0] = "ENTER"
c[1] = "NEW"
c[2] = "POINT"
c[3] = "FIRST"

cp数组:
cp[0] = c+3 指向 &c[3]
cp[1] = c+2 指向 &c[2]
cp[2] = c+1 指向 &c[1]
cp[3] = c 指向 &c[0]

cpp 初始 = &cp[0]

逐行一步步算

第一句:++cpp        cpp 从 &cp [0] → &cp [1]

               *cpp           取 cp [1] = c+2 = &c [2]

               **cpp        *&c[2] = c[2] = "POINT"输出:POINT

现在 cpp 停在:&cp[1]

输出:POINT       现在 cpp 停在:&cp[1]

第二句:++cpp         cpp 从 &cp [1] → &cp [2]

               *++cpp        取 cp [2] = c+1 = &c [1]

               –*++cpp     &c [1] 往前减 1 → &c [0]

               *–*++cpp  *&c[0] = "ENTER"

+3 指针往后跳 3 位:ENTER → 第 3 位开始 ER

输出:ER              现在 cpp 停在:&cp[2]

第三句:cpp[-2]       等价于 *(cpp – 2) cpp-2 = &cp [0] → * 就是 cp [0] = c+3 = &c [3]

              *cpp[-2] → *&c[3] = "FIRST"

              +3 往后跳 3 位:FIRST → ST

输出:ST               cpp 位置不变,还是 &cp [2]

第四句:cpp[-1] = *(cpp-1) → cp[1] = &c[2]

              cpp[-1][-1] = *(&c [2] – 1 ) = &c [1] → * 就是 "NEW"

              +1 往后跳 1 位:NEW → EW

输出:EW

最终输出结果如下:

POINT
ER
ST
EW

赞(0)
未经允许不得转载:171主机测评 » C语言指针精讲(六):sizeof与strlen核心对比、数组指针经典笔试题解析、指针运算高频笔试题解析
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址