当前位置: 首页 > news >正文

C语言基础:(十六)深入理解指针(6)

目录

前言

一、sizeof和strlen的对比

1.1  sizeof

1.2  strlen

1.3  sizeof和strlen的对比

二、数组和指针笔试题解析

2.1  一维数组

2.2  字符数组

        例题一:

        例题二:

        例题三:

        例题四:

        例题五:

        例题六:

2.3  二维数组

三、指针运算笔试题解析

3.1  题目一

3.2  题目二

 3.2.1  数据在内存中的对齐方式

3.2.2  结构体Test的内存布局

3.2.3  指针运算分析

(1)p + 0x1

(2)(unsigned long)p + 0x1

(3)(unsigned int*)p + 0x1

3.3  题目三

3.4  题目四

总结

前言

        本期是C语言基础中有关指针的最后一篇博客,本期将为大家介绍sizeof和strlen的对比、数组和指针乃至指针运算的笔试题解析。那么现在让我们开始吧!


一、sizeof和strlen的对比

1.1  sizeof

        在学习操作符的时候,我们学习了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;
}

1.2  strlen

        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;
}

1.3  sizeof和strlen的对比

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

二、数组和指针笔试题解析

        首先,我们来回顾一下数组名有何意义:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。

2.1  一维数组

        我们来通过下面这些例题,学会判断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));

        分析如下:

2.2  字符数组

        同样让我们来先看一些例题:

        例题一:

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));

        例题六分析结果如下:

2.3  二维数组

        分析下列代码的输出结果:

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]));

        分析结果如下:

三、指针运算笔试题解析

3.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;
} 
//程序的结果是什么?

        我们先来画个图分析一下:

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

3.2  题目二

//在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;
}

 3.2.1  数据在内存中的对齐方式

        在计算机系统中,对齐(Alignment) 是指数据在内存中的存储位置必须满足特定边界要求,以提高访问效率。不同的数据类型有不同的对齐规则,这会影响结构体的大小和内存布局。

        结构体的对齐方式受 最严格成员的对齐要求 影响,并可能插入 填充字节(Padding) 来满足对齐。规则总结如下:

  1. 结构体的对齐 = 最大成员的对齐值

    例如,如果结构体包含 int(4)、char(1)、double(8),则结构体按 8 字节对齐。
  2. 每个成员的偏移量必须是其对齐值的整数倍

    如果 int(4 字节对齐)放在偏移 2,编译器会插入 2 字节填充,使其移动到 4
  3. 结构体末尾可能需要填充,以保证数组对齐

    如果结构体大小不是对齐值的整数倍,编译器会在末尾填充,使得 sizeof(struct) 是最大对齐值的整数倍。

3.2.2  结构体Test的内存布局

        在x86环境下,默认是4字节对齐(32位系统),此时结构体Test的成员大小和对齐情况如下:

        因此Test的总大小=4(Num)+ 2(sDate)+ 2(cha)+ 8(sBa)= 20字节

3.2.3  指针运算分析

(1)p + 0x1

        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

(2)(unsigned long)p + 0x1

        (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

(3)(unsigned int*)p + 0x1

        这一步的计算与上一步同理,最后可得计算过程如下:

0x100000 + 1 * 4 = 0x100004

        最后的输出结果为:0x100004

        因此,最后整个代码的输出结果为:

0x100014    // p + 0x1(结构体指针运算)
0x100001    // (unsigned long)p + 0x1(纯数值运算)
0x100004    // (unsigned int*)p + 0x1(整型指针运算)

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;
}
//输出结果是什么?上述代码有什么问题?

        首先,代码中给二维数组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。

3.4  题目四

#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语言的学习之路还没有结束哦~~~

http://www.lryc.cn/news/623991.html

相关文章:

  • Centos 更新/修改宝塔版本
  • Rust 入门 生命周期(十八)
  • react echarts图表监听窗口变化window.addEventListener(‘resize’)与ResizeObserver()
  • 音乐创作魔法:解锁和弦与旋律的变化技巧
  • 3D打印——给开发板做外壳
  • 如何做HTTP优化
  • 【JAVA 核心编程】面向对象高级:类变量与方法 抽象类与接口
  • PowerPoint和WPS演示让多个对象通过动画同时出现
  • NY270NY273美光固态闪存NY277NY287
  • Portkey-AI gateway 的一次“假压缩头”翻车的完整排障记:由 httpx 解压异常引发的根因分析
  • duiLib 解决点击标题栏中按钮无响应问题
  • C# 反射和特性(自定义特性)
  • 健身房预约系统SSM+Mybatis实现(三、校验 +页面完善+头像上传)
  • RISC-V汇编新手入门
  • 【LeetCode】单链表经典算法:移除元素,反转链表,约瑟夫环问题,找中间节点,分割链表
  • 开发指南132-DOM的宽度、高度属性
  • HTTP0.9/1.0/1.1/2.0
  • SWE-bench:真实世界软件工程任务的“试金石”
  • 人工智能入门②:AI基础知识(下)
  • C++入门自学Day11-- String, Vector, List 复习
  • 如何利用gemini-cli快速了解一个项目以及学习新的组件?
  • 数据结构03(Java)--(递归行为和递归行为时间复杂度估算,master公式)
  • 人脸AI半球梯控/门禁读头的功能参数与技术实现方案
  • MySQL的事务基础概念:
  • 力扣刷题904——水果成篮
  • 黑马商城day08-Elasticsearch作业(个人记录、仅供参考、详细图解)
  • MLArena:一款不错的AutoML工具介绍
  • 【Linux】IO多路复用
  • SpringCloud 07 微服务网关
  • linux-高级IO(上)