【c语言】深度理解指针4——sizeof和strlen

文章目录

  • 一、sizeof和strlen对比
    • [1.1. sizeof](#1.1. sizeof)
    • [1.2. strlen](#1.2. strlen)
  • 二、指针部分练习
    • [2.1. 一维数组](#2.1. 一维数组)
    • [2.2. 字符数组](#2.2. 字符数组)
      • [2.2.1. 代码一](#2.2.1. 代码一)
      • [2.2.2. 代码二](#2.2.2. 代码二)
      • [2.2.3. 代码三](#2.2.3. 代码三)
      • [2.2.4. 代码四](#2.2.4. 代码四)
      • [2.2.5. 代码五](#2.2.5. 代码五)
      • [2.2.6. 代码六](#2.2.6. 代码六)
    • [2.3. 二维数组](#2.3. 二维数组)
  • 三、笔试题解析
    • [3.1. 题目一](#3.1. 题目一)
    • [3.2. 题目二](#3.2. 题目二)
    • [3.3. 题目三](#3.3. 题目三)
    • [3.4. 题目四](#3.4. 题目四)
    • [3.5. 题目五](#3.5. 题目五)
    • [3.6. 题目六](#3.6. 题目六)
    • [3.7. 题目七](#3.7. 题目七)

一、sizeof和strlen对比

1.1. sizeof

sizeof是单目操作符 ,算的是变量所占内存空间 的大小,单位是字节 ,如果是操作类型的话,计算的是使用类型创建的变量所占内存空间的大小,且sizeof后括号内有表达式的话,是不进行计算的.

sizeof只关注类型所占内存空间的大小,并不在乎内存中是什么数据

1.2. strlen

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

c 复制代码
size_t strlen ( const char * str )

返回类型是无符号整型,从str的第一个元素的地址开始向后查找,在\0之前元素的个数。

二、指针部分练习

2.1. 一维数组

c 复制代码
int a[] = {1,2,3,4};
1 printf("%d\n",sizeof(a));
2 printf("%d\n",sizeof(a+0));
3 printf("%d\n",sizeof(*a));
4 printf("%d\n",sizeof(a+1));
5 printf("%d\n",sizeof(a[1]));
6 printf("%d\n",sizeof(&a));
7 printf("%d\n",sizeof(*&a));
8 printf("%d\n",sizeof(&a+1));
9 printf("%d\n",sizeof(&a[0]));
10 printf("%d\n",sizeof(&a[0]+1));

将a数组初始化为1 2 3 4 ,在32位环境下:

  1. a是数组名,单独放在sizeof中,计算整个数组的大小,为16;
  2. a没有单独放在sizeof中,表示首元素地址,地址在内存中的大小为4字节,a+0找到的是1的地址,为4;
  3. *a拿到的是数组首元素1,大小为4字节;
  4. a+1为元素2的地址,大小为4字节;
  5. a [ 1 ] 得到的是元素2,大小为4字节;
  6. &a为整个元素的地址,类型为数组指针 int (*)[4],指针变量的大小为4字节;
  7. 两种思路:
    *&两者相互抵消,最终得到的是a,单独放在sizeof中表示数组的大小,为16字节;
    &a得到的是整个数组的地址,类型是数组指针int (*) [ 4 ],对数组指针进行解引用得到的是数组,因此算的是整个数组的大小,为16字节;
  8. 如图, &a + 1 表示跳过整个数组的长度,即4个元素的地址,类型还是数组指针,大小为4字节
  9. 取得的是第一个元素的地址,为指针变量,大小位4字节;
  10. 取得的是第二个元素的地址,为指针变量,大小为4字节.

2.2. 字符数组

2.2.1. 代码一

c 复制代码
char arr[] = {'a','b','c','d','e','f'};
1 printf("%d\n", sizeof(arr));
2 printf("%d\n", sizeof(arr+0));
3 printf("%d\n", sizeof(*arr));
4 printf("%d\n", sizeof(arr[1]));
5 printf("%d\n", sizeof(&arr));
6 printf("%d\n", sizeof(&arr+1));
7 printf("%d\n", sizeof(&arr[0]+1));
  1. 数组名单独放在sizeof中,得到的是整个数组的大小,为6字节;
  2. arr没有单独放在sizeof中,表示数组首元素地址,arr+0表示得到第一个元素的地址,为指针变量 ,大小为4字节;
  3. *arr得到数组首元素 ' a ',为字符类型,大小为1字节;
  4. arr [ 1 ] 表示数组第二个元素,为字符类型,大小为1字节;
  5. &arr表示拿到整个数组的地址,为数组指针char (*) [6] 类型,大小为4字节;
  6. &arr+1表示跳过整个数组,拿到 f 之后一个元素的地址,大小为4字节;
  7. &arr [ 0 ] + 1表示拿到第二个元素的地址,大小为4字节.

2.2.2. 代码二

c 复制代码
char arr[] = {'a','b','c','d','e','f'};
1 printf("%d\n", strlen(arr));
2 printf("%d\n", strlen(arr+0));
3 printf("%d\n", strlen(*arr));
4 printf("%d\n", strlen(arr[1]));
5 printf("%d\n", strlen(&arr));
6 printf("%d\n", strlen(&arr+1));
7 printf("%d\n", strlen(&arr[0]+1))

首先初始化数组

  1. arr为首元素地址,由于不知道字符结束的位置,也就是\0的位置,因此会造成越界访问,结果是随机值;
  2. arr + 0为首元素地址,\0位置不知道,会造成越界访问,结果随机;
  3. *arr得到的是字符a,a 的ASCII码值为97,相当于把97作为地址传给strlen,这块地址不属于arr,相当于野指针,程序会报错;
  4. arr [ 1 ]是字符b,相当于把b的ASCII码值98当作地址传给strlen,也是野指针,程序报错;
  5. &arr 拿到的是数组的地址,与数组首元素地址的值相同,从字符a向后直到\0,因此也是随机值;
  6. &arr+1表示跳过整个数组,得到f后一个元素的地址,开始向后寻找\0,也是随机值;
  7. &arr [ 0 ] + 1,表示得到b的地址,结果也是随机值;

2.2.3. 代码三

c 复制代码
char arr[] = "abcdef";
1 printf("%d\n", sizeof(arr));
2 printf("%d\n", sizeof(arr+0));
3 printf("%d\n", sizeof(*arr));
4 printf("%d\n", sizeof(arr[1]));
5 printf("%d\n", sizeof(&arr));
6 printf("%d\n", sizeof(&arr+1));
7 printf("%d\n", sizeof(&arr[0]+1));

"abcdef"为字符串结尾是\0,共有7个元素,类型为字符

  1. arr单独放在sizeof内部,表示整个数组的大小为7字节;
  2. arr + 0 为数组首元素地址,大小为4字节;
  3. *arr得到的是数组首元素a,大小为1字节;
  4. arr [ 1 ] 为元素b,大小为1字节;
  5. &arr为整个数组地址,为数组指针变量,大小为4字节;
  6. &arr为数组的地址,+1表示跳过整个数组,还是地址,大小为4字节;
  7. &arr[0]+1表示b的地址,大小为4字节;

2.2.4. 代码四

c 复制代码
char arr[] = "abcdef";
1 printf("%d\n", strlen(arr));
2 printf("%d\n", strlen(arr+0));
3 printf("%d\n", strlen(*arr));
4 printf("%d\n", strlen(arr[1]));
5 printf("%d\n", strlen(&arr));
6 printf("%d\n", strlen(&arr+1));
7 printf("%d\n", strlen(&arr[0]+1));
  1. 表示计算数组元素的个数,\0位置在 f 后,则元素个数为6;
  2. arr+0为数组首元素地址,向后到\0,共有6个元素;
  3. *arr得到的是首元素a, 表示将a的ASCII码值97作为地址传给strlen, 相当于野指针,程序报错;
  4. 同样报错;
  5. &arr得到数组的地址,值与数组首元素的地址相同,因此从首元素a向后直到\0,共有6个元素;
  6. &arr +1 ,跳过整个数组,\0位置未知,为随机值;
  7. &arr[0]+1 从字符b开始,共有5个元素;

2.2.5. 代码五

c 复制代码
char *p = "abcdef";
1 printf("%d\n", sizeof(p));
2 printf("%d\n", sizeof(p+1));
3 printf("%d\n", sizeof(*p));
4 printf("%d\n", sizeof(p[0]));
5 printf("%d\n", sizeof(&p));
6 printf("%d\n", sizeof(&p+1));
7 printf("%d\n", sizeof(&p[0]+1))

"abcdef"为字符串常量,不能被更改

p为字符指针变量,指向字符串首元素地址

  1. p为指针变量,大小为4字节;
  2. p+1为b的地址,大小为4字节;
  3. *p得到字符a,大小为1字节;
  4. p[0] == *( p + 0 ),表示得到字符串第一个元素a,大小为1字节;
  5. p为字符指针,类型为char*,&p表示字符指针p的地址,为二级指针,大小为4字节;
  6. &p+1表示跳过指针p的地址,本质还是指针,大小为4字节;
  7. &p[0]+1表示得到b的地址,大小为4字节.

2.2.6. 代码六

c 复制代码
char *p = "abcdef";
1 printf("%d\n", strlen(p));
2 printf("%d\n", strlen(p+1));
3 printf("%d\n", strlen(*p));
4 printf("%d\n", strlen(p[0]));
5 printf("%d\n", strlen(&p));
6 printf("%d\n", strlen(&p+1));
7 printf("%d\n", strlen(&p[0]+1));
  1. 6
  2. 从b开始算,共5个元素;
  3. *p为元素a,表示将a的ASCII码值97作为地址传给strlen,相当于野指针,程序报错;
  4. p [ 0 ]为元素a,表示将a的ASCII码值97作为地址传给strlen,相当于野指针,程序报错;
  5. &p表示得到一级指针p的地址,\0位置位置,为随机值;
  6. 依旧是随机值;
  7. &p[0]+1表示得到b的地址,向后共有5个元素.

2.3. 二维数组

c 复制代码
int a[3][4] = {0};
1 printf("%d\n",sizeof(a));
2 printf("%d\n",sizeof(a[0][0]));
3 printf("%d\n",sizeof(a[0]));
4 printf("%d\n",sizeof(a[0]+1));
5 printf("%d\n",sizeof(*(a[0]+1)));
6 printf("%d\n",sizeof(a+1));
7 printf("%d\n",sizeof(*(a+1)));
8 printf("%d\n",sizeof(&a[0]+1));
9 printf("%d\n",sizeof(*(&a[0]+1)));
10 printf("%d\n",sizeof(*a));
11 printf("%d\n",sizeof(a[3]));

在32位条件下:

  1. 数组名单独放在sizeof中,表示数组的大小,共12个元素,每个元素4字节,共48字节;
  2. a[ 0 ] [ 0 ] 表示第一个元素,为4字节;
  3. a[ 0 ] 表示二维数组第一行元素的数组名,数组名单独放在sizeof中表示整个数组的大小,第一行数组共4个元素,每个元素4字节,共16个字节;
  4. a[0]表示第一行数组的地址,+1表示跳过第一行数组,得到第二行数组的地址,为数组指针变量,大小为4字节;
  5. (a[0]+1)为第二行数组地址,解引用表示得到第二行数组,大小为16字节;
  6. a为数组首元素的地址,对于二维数组,首元素为第一行数组,则数组名a相当于第一行数组的地址,+1跳过第一行得到第二行数组的地址,为数组指针,大小为4字节;
  7. *(a+1)得到第二行数组,大小为16字节;
  8. a[ 0 ]表示第一行数组的数组名,&a[0]表示得到第一行数组的地址,+1跳过整个数组,得到第二行数组的地址,为数组指针,大小为4字节;
  9. *(&a[0]+1)表示得到第二行数组,大小为16字节;
  10. a相当于第一行数组的地址,*a得到第一行数组,大小为16字节;
  11. a [3]相当于得到第4行数组的地址,但是越界访问了,而sizeof计算的是类型在内存中所占的大小,不关心里面存的是什么数据,a[3]的类型还是数组指针类型,大小就是4字节.

三、笔试题解析

3.1. 题目一

c 复制代码
#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位整个数组地址,+1跳过整个数组,类型位数组指针,(int*)表示强制类型转换

ptr-1指向5的地址,解引用找到元素5

a+1指向元素2的地址,解引用找到2

答案为 2 5

3.2. 题目二

c 复制代码
//在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;
}
  1. p为结构体变量,+1跳过一个结构体,p + 0x1 == 0x100000 + 20 == 0x100014;
  2. 强制转换成无符号长整型,整型变量+1,就是正常+1,结果是0x100001;
  3. 强转为unsigned int*,+1跳过4字节,结果是0x100004.

3.3. 题目三

c 复制代码
#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;
}

第一行为逗号表达式,只有1,3,5存到了二维数组中,其余位置为0,a [0] 为第一行数组的数组名,p [0] == a[ 0 ] [ 0 ],为 1

3.4. 题目四

c 复制代码
//假设环境是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;
}

解释&p[4][2],结合图示:

  1. p为数组指针,指向第一行数组的地址,第一行数组有4个元素,每个元素为整型

  2. p[4][2] == *(*(p+4)+2),由于p指向的数组元素只有4个,因此+1跳过的是4个元素的地址,因此p+4跳过四行数组,指向第五行数组,解引用得到第五行数组名,+2得到第五行第三个元素,因此&p[4][2]表示第五行第三个元素的地址

  3. 二维数组元素在内存中是连续存放的,p存的是a数组第一行的地址,指向的数组列数为4,那么p[ ] [ 4 ]可以认为重新将数组排列成7行4列,因此p [ 4 ] [ 2 ] == 19,a [ 4 ] [ 2 ] == 23

  4. 地址-地址得到的是两地址之间元素的个数,因此 &p[4][2] 与 &a[4][2] 之间相差4个元素,小地址减去大地址得到的是-4
    对于本题

  5. %p 将-4打印成地址,-4的源码为
    10000000 00000000 00000000 00000100 源码
    111111111 111111111 111111111 111111011 反码
    10000000 00000000 00000000 00000100 补码
    计算机中存的是补码,转换成16进制为
    FFFFFFFC 就是结果

  6. 因此本题答案为 FFFFFFFC -4

3.5. 题目五

c 复制代码
#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;
}
  1. &aa+1跳过整个二维数组,强制转换成int*类型后,-1表示向前4个字节,因此,ptr1-1找到的是元素10的地址;
  2. aa为二维数组名,表示第一行数组的地址,+1找到第二行数组的地址,即元素6的地址,强制转换成int*在进行-1运算,表示向前4个字节,找到的是5的地址
    所以答案是10 5

3.6. 题目六

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

a是指针数组,存放指针的数组,共有三个元素,分别是w、a、a、的地址,a为数组首元素地址,也就是字符串"work"中'w'的地址,二级指针变量pa指向它,pa++后,指向"at"中'a'的地址,打印字符串得到的是 at.

3.7. 题目七

c 复制代码
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;
}


1.**++cpp : 前置++表示先++再使用,因此cpp先++后指向的是cp [ 1 ],解引用一次得到cp[1]存的地址,为c+2,再解引用得到字符串POINT注意:cpp现在指向的值cp[1]

  1. *--*++cpp+3 : + 操作符优先级最低,因此+3最后计算. cpp先++后指向的是cp[2]的地址,解引用得到cp[2],存的是c+1的地址,然后得到(*--(c+1)),先--,得到c,c指向的是E的地址,然后再*(c+3),得到字符串ER注意:cpp现在指向的值cp[2]

  2. *cpp[-2]+3 : cpp[-2] == *(cpp-2),得到cp[0],解引用找到的是c+3,指向字符F的地址,再+3向后跳过3个地址,找到S的地址,打印字符串得到ST

  3. cpp[-1][-1]+1cpp[-1][-1] == *(*(cpp-1)-1), 三级指针cpp-1找到cp[1]的地址,*(cpp-1)找到二级指针cp[1],存的是c+2的地址,再-1找到的是c+1,字符N的地址,+1跳过一个元素,打印的是EW.


相关推荐
h汉堡26 分钟前
C++入门基础
开发语言·c++·学习
HtwHUAT1 小时前
实验四 Java图形界面与事件处理
开发语言·前端·python
鄃鳕1 小时前
QSS【QT】
开发语言·qt
碎梦归途1 小时前
23种设计模式-结构型模式之外观模式(Java版本)
java·开发语言·jvm·设计模式·intellij-idea·外观模式
_GR1 小时前
2025年蓝桥杯第十六届C&C++大学B组真题及代码
c语言·数据结构·c++·算法·贪心算法·蓝桥杯·动态规划
muyouking111 小时前
4.Rust+Axum Tower 中间件实战:从集成到自定义
开发语言·中间件·rust
FAREWELL000752 小时前
C#进阶学习(九)委托的介绍
开发语言·学习·c#·委托
我该如何取个名字2 小时前
Mac配置Java的环境变量
java·开发语言·macos
kkkkatoq2 小时前
Java中的锁
java·开发语言