热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【妙】bug称它为数组越界的妙用

1、聊一聊首先跟大家推荐一首非常温柔的歌曲,跑步的常听。本文主要把自己对C语言中柔性数组、零数组等等的理解分享给大家,并聊聊如何构建一种统一化的学习思想

1、聊一聊

    

    首先跟大家推荐一首非常温柔的歌曲,跑步的常听。

    本文主要把自己对C语言中柔性数组、零数组等等的理解分享给大家,并聊聊如何构建一种统一化的学习思想。

2、正文部分

1

抽象与归纳

拥有多年开发经验的工程师们,除了个别人技术上老是在吃老本而止步现状的落后以外,大部分技术水平都差不多。

这些经验老道的攻城狮们,通常对待疑难杂症都有一套经常使用的解决方案,这也是为什么对于一些常规的设计他们能够信手拈来的原因。

然而这些解决方案虽然最终都能解决问题,但却存在时间、实施难度等方面的差异,往往这样的差异就被大家拿来作为技术水平高低的评价。

这些差异到底是什么导致的呢?个人觉得还是对问题的本质抽象和问题归纳能力存在一些差别。

所以今天bug菌把数组越界、柔性数组、零数组等这些概念统一起来,对一件事物不同的理解角度会收获不一样的知识。

2

把脉"奇怪"数组

1

零数组与柔性数组

大家有时候看一些开源的代码特别是Linux源码中会发现一些"奇怪"的数组定义,如array[0],array[]等等,他们一般位于结构体的末尾来定义使用,用变长数组在网络通信中构造不定长数据包,如下代码所示。

struct _tag_Pack {uint16_t type;uint16_t len;uint8_t  data[0];
} __attribute ((packed));

其实对于0数据的使用在标准的C语言中是非法的,当然目前最新的C标准应该有这一块的支持,至少bug菌在VS2019上它只报了一个警告。

而柔性数组是C99标准中支持的,即array[]形式,而零数组主要是GUNC中的扩展语法,当然对于目前非常多的编译器已经兼容了这一块,所以在相关开源代码中是非常常见的,一些朋友首次看到可能迷迷糊糊,或者有些妙招还没有get到,就好好看看下面bug菌唠的嗑。

2

经典实例

代码已为你准备好了:

#include 
#include typedef unsigned char uint8_t;
typedef unsigned short uint16_t;//零数组 
struct _tag_Pack1 {uint16_t type;uint16_t len;uint8_t  data[0];
} __attribute ((packed));//柔性数组C99支持 
struct _tag_Pack2 {uint16_t type;uint16_t len;uint8_t  data[];
} __attribute ((packed));//数组长度为1 
struct _tag_Pack3 {uint16_t type;uint16_t len;uint8_t  data[1];
} __attribute ((packed));//指针代替 
struct _tag_Pack4 {uint16_t type;uint16_t len;uint8_t  *data;
} __attribute ((packed));int main(int argc, char *argv[]) {struct _tag_Pack1 *Pack1 = (struct _tag_Pack1*)malloc( sizeof(struct _tag_Pack1) + 10);struct _tag_Pack2 *Pack2 = (struct _tag_Pack2*)malloc( sizeof(struct _tag_Pack2) + 10);struct _tag_Pack3 *Pack3 = (struct _tag_Pack3*)malloc( sizeof(struct _tag_Pack3) + 10);struct _tag_Pack4 *Pack4 = (struct _tag_Pack4*)malloc( sizeof(struct _tag_Pack4)); printf("Pack1 Size : %d\n",sizeof(struct _tag_Pack1));printf("Pack2 Size : %d\n",sizeof(struct _tag_Pack2));printf("Pack3 Size : %d\n",sizeof(struct _tag_Pack3));printf("Pack4 Size : %d\n",sizeof(struct _tag_Pack4));printf("Pack1 Offset : %ld\n", ((long)(Pack1->data ) - (long)(Pack1)));printf("Pack2 Offset : %ld\n", ((long)(Pack2->data ) - (long)(Pack2)));printf("Pack3 Offset : %ld\n", ((long)(Pack3->data ) - (long)(Pack3)));printf("Pack4 Offset : %ld\n", ((long)(&Pack4->data )- (long)(Pack4)));free(Pack1);free(Pack2);free(Pack3);free(Pack4);printf("最后一个bug\n");return 0;
}

运行结果如下

3

终极图解

如果经常使用此类方法应该很快就可以看懂了,如果没看懂,那就再来几张图:

1 ) 对于结构体类型Pack1和Pack2用法上几乎没什么差异,只是遵循的标准不同罢了,data[0]和data[]都不占据内存,你可以认为其仅仅只是一个地址常量罢了。

在GUNC标准中建议大家使用[0],而C99标准的VC编译环境中可以使用柔性数组方式[],这样移植性可能会更好一点。

2 ) 对于Pack3定义方式或许很多人初次见到,感觉没有太大的意义,无非就是在结构体最后使用了一个1byte的数组,其实这种形式通过浪费了一个byte却获得了代码非常好的兼容性,因为一些编译器并不支持零数组和柔性数组形式,而通过最后[1]的形式可以兼容各个标准和跨编译器,非常nice!

3)最后当然要谈到大伙最容易想到使用指针来代替数组了,这种形式先不说相对前面的结构体会浪费一个指针占据的空间,最重要的是无法通过一次malloc分配好所有内存,第一次malloc先为结构体成员分配内存,第二次malloc分配指针所指向的内存,并且两次malloc并不是在同一块连续的内存区域,容易造成较多内存碎片,而且释放内存还需要分别释放,我好累!

2

一切皆越界

数组越界应该是大伙最早出现且记忆犹新的bug了, C语言就是这么牛掰,可以说无论你想以怎样一种花式访问内存,C语言均可以实现,当然这把大刀你拿不动,却不老实想耍耍,一不小心就有可能砍伤自己。

而今天这几个看似奇奇怪怪的数组,我统一把他们理解为数组越界,并不需要你单独的去记忆每种数组如何的使用和分析。

我们再回到文章开头聊到工作多年水平的问题。

一个人的抽象和归纳能力的提升不仅仅需要多年的经验积累,更需要一个字 -- ”悟”。电影里面经常有这样的桥段,同样是拿到一本武功秘籍,却每个人练出来的境界各异。‘

刚参加工作的几年或许你能够到各类书本,培训机构等等,快速的提高自身的技术水准,因为在这个阶段是你技术知识匮乏的时候,知识的填充能够让你理解和认清楚很多的技术。

然而随着时间的推移,知识结构体系的完善,便会遇到技术的提升瓶颈,甚至感觉力不从心。

或许到了一个需要"悟"的阶段!

至于怎么去悟,bug菌也不知道,也或许就是坐下来认真思考吧!我相信也没有人能够说清楚,也正因为其没有方法可寻才愈发宝贵。

一旦学会了悟,便能够助你快速获取统一化的知识结构,并且从中获得更为高深的技术知识和解决方案,看问题也就会更加的清晰明朗!

3、结束语

    

    好了,本文到此结束!希望本文能够给你带来一些收获!

    我是bug菌,如果有所收获,记得点个赞再走!

推荐专辑  点击蓝色字体即可跳转

☞  MCU进阶专辑 

☞  嵌入式C语言进阶专辑 

☞  “bug说”专辑 

☞ 专辑|Linux应用程序编程大全

☞ 专辑|学点网络知识

☞ 专辑|手撕C语言

☞ 专辑|手撕C++语言

☞ 专辑|经验分享



推荐阅读
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • 包含phppdoerrorcode的词条 ... [详细]
  • 浅析python实现布隆过滤器及Redis中的缓存穿透原理_python
    本文带你了解了位图的实现,布隆过滤器的原理及Python中的使用,以及布隆过滤器如何应对Redis中的缓存穿透,相信你对布隆过滤 ... [详细]
  • malloc 是 C 语言中的一个标准库函数,全称为 memory allocation,即动态内存分配。它用于在程序运行时申请一块指定大小的连续内存区域,并返回该区域的起始地址。当无法预先确定内存的具体位置时,可以通过 malloc 动态分配内存。 ... [详细]
  • 本文节选自《NLTK基础教程——用NLTK和Python库构建机器学习应用》一书的第1章第1.2节,作者Nitin Hardeniya。本文将带领读者快速了解Python的基础知识,为后续的机器学习应用打下坚实的基础。 ... [详细]
  • 本文总结了《编程珠玑》第12章关于采样问题的算法描述与改进,并提供了详细的编程实践记录。参考了其他博主的总结,链接为:http://blog.csdn.net/neicole/article/details/8518602。 ... [详细]
  • WinMain 函数详解及示例
    本文详细介绍了 WinMain 函数的参数及其用途,并提供了一个具体的示例代码来解析 WinMain 函数的实现。 ... [详细]
  • 本文回顾了作者初次接触Unicode编码时的经历,并详细探讨了ASCII、ANSI、GB2312、UNICODE以及UTF-8和UTF-16编码的区别和应用场景。通过实例分析,帮助读者更好地理解和使用这些编码。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • ARM汇编基础基于Keil创建STM32汇编程序的编写
    文章目录一、新建项目(1)工具介绍(2)创建项目:二、配置环境(1)配置芯片&#x ... [详细]
  • 单片机入门指南:基础理论与实践
    本文介绍了单片机的基础知识及其应用。单片机是一种将微处理器(类似于CPU)、存储器(类似硬盘和内存)以及多种输入输出接口集成在一块硅片上的微型计算机系统。通过详细解析其内部结构和功能,帮助初学者快速掌握单片机的基本原理和实际操作方法。 ... [详细]
  • 2022年7月20日:关键数据与市场动态分析
    2022年7月20日,本文对当日的关键数据和市场动态进行了深入分析。主要内容包括:1. 关键数据的解读与趋势分析;2. 市场动态的变化及其对投资策略的影响;3. 相关经济指标的评估。通过这些分析,帮助读者更好地理解当前市场环境,为决策提供参考。 ... [详细]
  • 开发日志:高效图片压缩与上传技术解析 ... [详细]
  • 本文详细解析了客户端与服务器之间的交互过程,重点介绍了Socket通信机制。IP地址由32位的4个8位二进制数组成,分为网络地址和主机地址两部分。通过使用 `ipconfig /all` 命令,用户可以查看详细的IP配置信息。此外,文章还介绍了如何使用 `ping` 命令测试网络连通性,例如 `ping 127.0.0.1` 可以检测本机网络是否正常。这些技术细节对于理解网络通信的基本原理具有重要意义。 ... [详细]
  • 深入解析C语言中结构体的内存对齐机制及其优化方法
    为了提高CPU访问效率,C语言中的结构体成员在内存中遵循特定的对齐规则。本文详细解析了这些对齐机制,并探讨了如何通过合理的布局和编译器选项来优化结构体的内存使用,从而提升程序性能。 ... [详细]
author-avatar
手机用户2502912857
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有