数组存储与指针学习笔记(一)数据类型与存储、数据对齐、数据移植、typedef

news2024/11/15 7:55:01

数据类型与存储

    • 一、数据类型与存储
      • 1.1 大端模式与小端模式
      • 1.2 有符号数和无符号数
      • 1.3 数据溢出
      • 1.4 数据类型转换
    • 二、数据对齐
      • 2.1 为什么非要地址对齐
      • 2.2 结构体对齐
      • 2.3 联合体对齐
    • 三、数据的可移植性
    • 四、Linux内核中的size_t类型
    • 五、typedef
      • 5.1 typedef的基本用法
      • 5.2 typedef的优势
      • 5.3 typedef的作用域
      • 5.4 typedef的适用范围

一、数据类型与存储

  类型,是一组数值及对该组数值进行各种操作的集合。同一种类型的数据,在不同的处理器平台下,存储方式可能不一样。不同类型的数据,在同一个处理器平台下,存储方式和运算规则也可能不一样。很多人都说指针是C语言的灵魂,我认为存储才是C语言的精髓和灵魂。
  认识一下ANSI C关键字 和 C99/C11新增关键字。关键字里,除了控制程序结构的一些关键字,绝大部分都与数据类型和存储相关。
在这里插入图片描述
在这里插入图片描述

1.1 大端模式与小端模式

  在计算机中,位(bit)是最小的存储单位,通常使用一个电容器来表示:充电时高电位表示1,放电时低电位表示0。8个bit组成一字节(Byte),字节是计算机最基本的存储单位,也是最小的寻址单元,计算机通常以字节为单位进行寻址。字(Word)代表计算机处理指令或数据的二进制数位数,是计算机进行数据存储和数据处理的运算的单位。在一个32位的计算机系统中,通常4字节组成一个字(Word),字是软件开发者常用的存储单位。(注意:一个字并不都是占4字节,通常由系统硬件(总线,CPU命令字位数等)有关,在16位的系统中(比如8086微机) 1字 (word)= 2字节(byte)= 16(bit),在32位的系统中(比如win32) 1字(word)= 4字节(byte)=32(bit),在64位的系统中(比如win64)1字(word)= 8字节(byte)=64(bit))。

  • 字节序
      不同字节的数据在内存中的存储顺序被称为字节序。根据字节序的不同,我们一般将存储模式分为大端模式和小端模式。

    • 大端模式:高地址存储高字节数据,低地址存储低字节数据。
    • 小端模式:高地址存储低字节数据,低地址存储高字节数据。
      在这里插入图片描述
        常用的处理器中,ARM、X86、DSP一般都采用小端模式,而IBM、Sun、PowerPC架构的处理器一般都采用大端模式。
        如何判断程序运行的当前平台是大端模式还是小端模式呢?很简单,我们只需要将一个整型变量赋值给字符型变量,通常会发生"截断",将会给低8位的字节赋值到字符型变量,通过打印就可以判断是大端模式还是小端模式。
    #include <stdio.h>
    
    int main(void)
    {
    	int a = 0x11223344;
    	char b;
    	b = a;
    	if(b==0x44)
    		printf("Little endian\r\n");
    	else
    		printf("Big endian\r\n");
    	return 0;
    }
    
  • 位序
      位序指在一字节的存储中,各个比特位的存储顺序。以十六进制数据0x78=01111000(B)为例,其在内存中可能有2种存储方式。一般情况下字节序和位序是一一对应的。小端模式下,低端地址存储低字节数据,在一字节中,bit0地址也用来存储这个字节的bit0位。大端模式则相反,bit0用来存储一字节的高比特位。
    在这里插入图片描述
      一般来讲,小端模式低地址存储低字节数据,比较符合人类的思维习惯;而大端模式则更适合计算机的处理习惯:不需要考虑地址和数据的对应关系,以字节为单位,把数据从左到右,按照由低到高的地址顺序直接读写即可。大端模式一般用在网络字节序、各种编解码中。
      作为一名嵌入式工程师,掌握大端模式与小端模式的存储方式很有必要。在开发移植过程当中,如配置寄存器、网络数据传输、移植网络等,需要考虑大小端模式的转换。在一个嵌入式软件中,如何实现大小端模式的转换过程,示例代码如下:

#define swap_endian_u16(A) \                       ((A & 0xFF00 >> 8)|(A & 0x00FF << 8))

1.2 有符号数和无符号数

  C语言为了能表示负数,引入了有符号数和无符号数的概念,在声明数据类型时分别使用关键字signed和unsigned修饰。我们定义的变量如果没有使用signed或unsigned显式修饰,默认是signed型的有符号数。
  一个字符型的有符号数,最高的位bit7是符号位:0表示正数,1表示负数,其余的比特位用来表示大小。而一个字符型的无符号数,所有的比特位都用来表示数的大小。有符号数和无符号数能表示的数值范围是不一样的,对于一个字符型数据而言,有符号数能表示的数值范围为[-128,127],而无符号数的数值范围为[0,255]。可以使用%d和%u格式符分别格式化打印有符号数和无符号数。
  一个存储在物理内存中的数据,可以被看作一个有符号数,也可以被看作一个无符号数,就看你怎么去解析它:你使用格式符%d打印,printf()函数就把它看作一个有符号数;你使用无符号格式符%u打印,printf()函数就把它看成了另外一个数。
正所谓“看山是山,看水是水;看山不是山,看水不是水”。如下程序所示
在这里插入图片描述在这里插入图片描述

  •   无符号数在计算机内存中存储时,所有的比特位都用来表示数的大小,没有原码、补码之说。
  •   有符号数,则采用补码形式存储,一个有符号数有原码、反码、补码之说。
反码 = 符号位保持不变,所有的数据位取反。
补码 = 反码 + 1。
正数的补码 = 原码
负数补码 = 反码 + 1

  提问:为啥采用补码存储,而不全使用原码?
  答:1、解决了0的编码问题。如果所有的数据都使用原码编码,那么+0和-0的编码分别为00000000和10000000,一个数用两个编码表示,编码就出现了问题。采用补码则可以避免这个问题,+0和-0都使用00000000表示,空下的编码10000000就可以多表示一个数:-128。需要注意的是,-128这个数只有补码,没有原码和反码
  2、它可以将减法运算转换为加法运算,省去了CPU减法逻辑电路的实现,CPU只需要实现全加器、求补电路即可同时支持加法运算和减法运算。如下例子所示
  正常计算下在这里插入图片描述
  我们将其改为加法运算:7+(-3),则省去了减法电路,直接使用加法电路运算即可。
在这里插入图片描述
  有符号数在运算过程中,符号位也是参与运算的,和其他数据位的计算遵循相同的计算法则和进位处理。用补码表示的数据相加,当最高位有进位时,进位直接被丢弃。

1.3 数据溢出

  每一种数据类型都有它能表示的数值范围。

  • 无符号数溢出时会进行取模运算,继续“周期轮回”。
      例如一个unsigned char类型的数据,它能表示的数据范围为[0,255],当其循环到255最大值时继续加1,这个数就变成了0,开始新的一轮循环,周而复始。
    在这里插入图片描述程序运行结果如下:
    在这里插入图片描述

  • 有符号数,当发生数据溢出时,由于C语言的语法宽松性,不对数据类型做安全性检查,因此也不会触发异常,但是会产生一个未定义行为。未定义行为,通俗点理解,就是遇到这种情况时,C语言标准也没有规定该如何操作,各家编译器在处理这种情况时也就没有了参考标准,各自按照自己的方式处理,编译器都不算错误。这也导致了当有符号数发生溢出时,运行结果是不确定的,在不同的编译器环境下编译运行,结果可能不一样

  因此,数据溢出可能会导致程序的运行结果与预期不一致。
  防范数据溢出方法:

  • 1、两个有符号数相加的情况。如果两个正数相加的和小于0,说明运算过程中发生了数据溢出。同理,如果两个负数相加的和大于0,也说明数据发生了溢出。
  • 2、无符号数的相加,如果两个数的和小于其中任何一个加数,此时我们也可以判断数据在计算过程中发生了溢出现象。

1.4 数据类型转换

  数据类型转换分为两种:一种是隐式类型转换,一种是强式类型转换。如果程序员在程序中没有对类型进行强式类型转换,则编译器在编译程序时就会自动进行隐式类型转换。
  一个C程序中发生隐式类型自动转换,主要是以下几种情况。

  • 算术运算、逻辑运算、赋值表达式中运算符两侧数据类型不相同时。
  • 函数调用过程中,传递的实参和形参类型不匹配时。
  • 函数返回值类型与函数声明的类型不匹配时。
      编译器遇到以上情况,就会对数据类型进行自动转换,即隐式类型转换。转换规则一般按照从低精度向高精度、从有符号数向无符号数方向转换。在这里插入图片描述  一个有符号数和无符号数比较大小时,编译器会将它们两个都转换为无符号数。
      强制类型转换的过程中需要注意一个问题,数据的值在转换过程中可能会发生改变:在将一个char型数据转换为int型数据时,值保持不变,但存储格式发生了变化,将char型数据保存在32位中的低8位地址空间,其余的高24位使用符号位填充。将一个有符号数转换为无符号数时,数据的存储格式不会发生变化,但是值会发生改变,因为此时有符号数的符号位变成了无符号数的数据位。

二、数据对齐

2.1 为什么非要地址对齐

  数据对齐原则,就是C语言中各种基本数据类型要按照自然边界对齐:一个char型的变量按1字节对齐,一个short型的整型变量按sizeof(short int)字节对齐,一个int型的整型变量要按sizeof(int)字节对齐。每种数据类型的对齐字节数一般也被称为对齐模数。
  为什么非要地址对齐呢?这主要是由CPU硬件决定的。不同处理器平台对存储空间的管理不同,为了简化CPU电路设计,有些CPU在设计时简化了地址访问,只支持边界对齐的地址访问,因此编译器也会根据处理器平台的不同,选择合适的地址对齐方式,以保证CPU能正常访问这些存储空间。

2.2 结构体对齐

   C语言的基本数据类型不仅要按照自然边界对齐,复合数据类型(如结构体、联合体等)也要按照各自的对齐原则对齐。

  • 结构体内各成员按照各自数据类型的对齐模数对齐。
  • 结构体整体对齐方式:按照最大成员的size或其size的整数倍对齐。
      因为结构体内各个成员都要按照自身数据类型的对齐模数对齐,所以在结构体内部难免会有“空洞”产生,导致结构体的大小也不一样。结构体之所以要对齐,根本原因就是为了加快CPU访问内存的速度,在具体实现上,一般都采用每种数据类型的默认对齐模数sizeof(type)对齐。
      如果在结构体里内嵌其他结构体,那么结构体作为其中一个成员也要按照自身类型的对齐模数对齐。结构体自身的对齐模数是该结构体中最大成员的size,或者其size的整数倍。

2.3 联合体对齐

  联合体也有自己的对齐原则。

  • 联合体的整体大小:最大成员对齐模数或对齐模数的整数倍。
  • 联合体的对齐原则:按照最大成员的对齐模数对齐。

  在C程序编译过程中,无论是基本数据类型还是复合数据类型,编译器在为各个变量分配地址空间时,会按照大家各自的默认对齐模数进行地址对齐。除此之外,我们也可以通过#pragma预处理命令或GNU C编译器的aligned/packed属性声明来显式指定对齐方式。

三、数据的可移植性

  我们可以使用sizeof关键字去查看int类型的数据在内存中的大小,在不同的编译环境下编译上面的程序并运行,你会发现运行结果可能不一样。在一个跨平台的程序中,有时候我们会需要一个固定大小的存储空间,或者一个固定长度的数据类型。
  现在的操作系统一般都支持多种CPU架构、多种处理器平台。操作系统为了实现跨平台运行,一般都会考虑数据的可移植性,如大小端存储模式、数据对齐、字长等。我们在编程时,可以把程序中与系统、平台相关的部分隔离封装在一个单独的头文件或配置文件中,整个程序的可移植部分和不可移植部分也就变得泾渭分明,更加方便后续的管理、维护和升级。

四、Linux内核中的size_t类型

  Linux内核中定义了很多变量,使用了各种不同的数据类型,总的来说,可以分为3类。

  • C语言基本数据类型:int、char、short。
  • 长度确定的数据类型:long。
  • 特定内核对象的数据类型:pid_t、size_t。
      数据类型size_t一般使用#define宏定义,后面使用一个_t的后缀表示Linux内核中在某些地方特定使用的数据类型。
    在这里插入图片描述
      size_t数据类型一般用在表示长度、大小等无关正负的场合,如数组索引、数据复制长度、大小等。使用size_t不仅仅是考虑到数据类型的可移植性,size_t的另一个优点是其大小并非是固定的,而是用来表征针对某平台的最大长度。当我们使用无符号型的size_t用来表示一个地址或者数据复制的长度时,根本不用担心它表示的数值范围够不够用。

五、typedef

5.1 typedef的基本用法

  使用typedef关键字,可以给student声明一个别名student_t和一个结构体指针类型student_ptr,然后可以直接使用student_t类型去定义一个结构体变量,不用再写struct,这样会显得代码更加简捷。
在这里插入图片描述  程序的运行结果如下:
在这里插入图片描述  typedef除了与结构体结合使用,还可以与数组结合使用。定义一个数组,通常使用int array[10];即可。我们也可以使用typedef先声明一个数组类型,然后使用这个类型去定义一个数组。声明了一个数组类型array_t,然后使用该类型定义一个数组array,这个array效果其实就相当于int array[10]
在这里插入图片描述  typedef还可以与指针结合使用PCHAR的类型是char*,我们使用PCHAR类型去定义一个变量str,其实就是一个char*类型的指针。
在这里插入图片描述  typedef还可以和函数指针结合使用。定义一个函数指针,我们通常采用下面的形式。
在这里插入图片描述  在实际编程中,typedef还可以与枚举结合使用。枚举与typedef的结合使用方法和结构体类似:可以使用typedef为枚举类型color声明一个新名称color_t,然后使用这个类型就可以直接定义一个枚举变量。

5.2 typedef的优势

  • 可以让代码更加清晰简捷。
    在这里插入图片描述

  • 增加代码的可移植性。
    在这里插入图片描述
      如果我们在代码中想使用一个32位的固定长度的无符号类型数据,则可以使用上面的方式声明一个U32的数据类型,在程序中你就可以放心大胆地使用U32。当将代码移植到不同的平台时,直接修改这个声明就可以了。

  • 比宏定义更好用。
      C语言的预处理指令#define用来定义一个宏,而typedef则用来声明一种类型的别名。typedef和宏相比,不是简单的字符串替换,而是可以使用该类型同时定义多个同类型对象。
    在这里插入图片描述

  • 让复杂的指针声明更加简捷。
       我们可以使用typedef优化一下:先声明一个函数指针类型func_ptr_t,接着定义一个数组,就会更加清晰简捷,可读性它增加了不少。在这里插入图片描述   typedef也是一个存储类关键字。typedef在语法上是一个存储类关键字。和常见的存储类关键字(如auto、register、static、extern)一样,在修饰一个变量时,不能同时使用一个以上的存储类关键字,否则编译会报错。
    在这里插入图片描述

5.3 typedef的作用域

   和宏的全局性相比,typedef作为一个存储类关键字,是有作用域的。使用typedef声明的类型和普通变量一样,都遵循作用域规则,包括代码块作用域、文件作用域等。
在这里插入图片描述
宏定义在预处理阶段就已经替换完毕,是全局性的,只要保证引用它的地方在定义之后就可以了。而使用typedef声明的类型则和普通变量一样,都遵循作用域规则。
在这里插入图片描述

5.4 typedef的适用范围

一般来讲,当遇到以下情形时,使用typedef可能会比较合适,否则可能会适得其反。

  • 创建一个新的数据类型。
  • 跨平台的指定长度的类型,如U32/U16/U8。
  • 与操作系统、BSP、网络字宽相关的数据类型,如size_t、pid_t等。
  • 不透明的数据类型,需要隐藏结构体细节,只能通过函数接口访问的数据类型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/506310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python操作集合

# 集合 l{1,2,1} print(l) sset(range(5)) print(s)# 判断in 或 not in print(5 not in l) # 集合元素新增操作 l.add(4) l.update(1,3,6) print(l) l.update((1,3,5)) l.update([4,4,6]) # 删除集合元素 l.remove(2) l.discard(2) # 无目的的删除 自己不带参数 l.pop() l.cl…

【跟着陈七一起学C语言】今天总结:C预处理器和C库

友情链接&#xff1a;专栏地址 知识总结顺序参考C Primer Plus&#xff08;第六版&#xff09;和谭浩强老师的C程序设计&#xff08;第五版&#xff09;等&#xff0c;内容以书中为标准&#xff0c;同时参考其它各类书籍以及优质文章&#xff0c;以至减少知识点上的错误&#x…

大语言模型(LLM)和基于人类反馈的强化学习(RLHF)

只需三步&#xff0c;构建你的LLM 预训练语言模型 L L M S S L LLM^{SSL} LLMSSL&#xff08;self-supervised-learning)(指令)监督微调预训练模型 L L M S F T LLM^{SFT} LLMSFT&#xff08;supervised-fine-tuning)基于人类反馈的强化学习微调 L L M R L LLM^{RL} LLMRL&…

K8s常用命令

Namespace 默认情况下&#xff0c;kubernetes集群中的所有的Pod都是可以相互访问的。但是在实际中&#xff0c;可能不想让两个Pod之间进行互相的访问&#xff0c;那此时就可以将两个Pod划分到不同的namespace下。kubernetes通过将集群内部的资源分配到不同的Namespace中&#…

猪场规模怎样划分?类型都有哪些?

养猪场按照经营方式分为大中小猪场&#xff08;猪场规模&#xff09;和集团猪场。集团猪场是指集团化发展与管理的养猪企业&#xff0c;或者简称为集团化养猪企业&#xff0c;重点在于“集团化”。猪场规模则是按照年出栏数量划分。 小规模猪场&#xff1a;年出栏3000头以下&a…

加密芯片在GCP系统的应用方案

物联网&#xff08;IoT&#xff09;设备正在迅速发展&#xff0c;越来越多的设备连接到互联网并与其他设备进行通信。这使得设备的安全变得更加重要&#xff0c;因为它们可能会暴露敏感的数据和功能。Google Cloud IoT Core&#xff08;GCP&#xff09;是一个完全托管的服务&am…

Easydict 简洁易用的翻译词典,带你轻松优雅地查找单词或翻译文本。

Easydict Easydict 是一个简洁易用的翻译词典 macOS App&#xff0c;能够轻松优雅地查找单词或翻译文本。Easydict 开箱即用&#xff0c;能自动识别输入文本语言&#xff0c;支持输入翻译&#xff0c;划词翻译和 OCR 截图翻译&#xff0c;可同时查询多个翻译服务结果&#xff…

喜报丨迪捷软件荣获浙江省专精特新荣誉称号

近日&#xff0c;根据工业和信息化部《优质中小企业梯度培育管理暂行办法》&#xff08;工信部企业〔2022〕63号&#xff09;和《浙江省经济和信息化厅关于印发浙江省优质中小企业梯度培育管理实施细则&#xff08;暂行&#xff09;的通知》&#xff08;浙经信企业〔2022〕197号…

光耦合器的输入电路及重要参数

光耦合器是一种通过使用光能将输入控制信号耦合到输出或负载的器件&#xff0c;其方式使得输入信号和负载&#xff08;输出&#xff09;之间的电气隔离也保持不变。光耦合器的基本功能是通过光能耦合输入和输出电路&#xff08;因此称为光耦合器&#xff09;&#xff0c;并在输…

Monaco Editor编辑器教程(三十):将vue文件作为一种编程语言集成到monaco editor中,实现vue组件的语法,标签高亮。

前言 最近在使用gitlab的web ide时发现当编写一个 vue组件时,文件的后缀名为.vue。在编辑器的右上角会显示当前的编程语言时vue,并且高亮语法或标签,格式化,折叠都表现很优秀。但是其实monaco是不支持vuejs的,作为一名前端开发者,我们有时需要在monaco编辑器中编写vue组…

操作系统学习02

&#xff01;&#xff01;&#xff01;由于感冒和出去玩&#xff0c;好几天没学这些计算机基础知识了&#xff01;&#xff01;&#xff01; 抓紧跟上嘿嘿嘿 1、内存管理主要做了什么 操作系统的内存管理非常重要&#xff0c;主要负责下面这些事情&#xff1a; 内存的分配与…

Windows服务器配置开机自启动

有两种简单实现的方式。 第一种&#xff0c;直接将可执行文件或脚本的快捷方式放置到 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp 这个文件夹里&#xff0c;服务器在启动的时候会自动执行。 第二种&#xff0c;配置为windows系统服务 利用winsw来将可…

HMC408LP3ETR-ASEMI代理亚德诺HMC408LP3ETR原厂芯片

编辑-Z HMC408LP3ETR参数描述&#xff1a; 型号&#xff1a;HMC408LP3ETR 频率范围&#xff1a;5.1 - 5.9 GHz 增益&#xff1a;20 dB 输入回波损耗&#xff1a;8 dB 输出回波损耗&#xff1a;6 dB 1dB压缩的输出功率&#xff1a;27 dBm 饱和输出功率&#xff1a;31 dB…

鬼畜提问变身指南:ChatGPT十个打破常规的提问公式

Chatgpt的恐怖之处不在于它有多么的准确&#xff0c;很多时候它的回答甚至充满常识性错误&#xff0c;比如你问美国为什么轰炸珍珠岛它都能一本正经的回答你&#xff08;这当然也有中文语料数据投喂不足和中文本身就复杂而难以理解的原因&#xff0c;听说用英文提问的准确性会提…

openeuler 22.03 制作openssh9.3p1 rpm升级包和升级实战

一、背景说明 openeuler 22.03 默认安装的openssh 版本为8.8p1&#xff0c;经绿盟扫描&#xff0c;存在高危漏洞&#xff0c;需要升级到最新。 官网只提供编译安装包&#xff0c;而openeuler 22.03 为rpm方式安装。 为了方便升级&#xff0c;先通过编译安装包&#xff0c;制…

OpenCV中的图像处理3.7-3.8(五)边缘检测、图像金字塔

目录 3.7 边缘检测目标理论OpenCV中的Canny边缘检测其他资源练习 3.8 图像金字塔目标理论使用金字塔进行图像混合其他资源 翻译及二次校对&#xff1a;cvtutorials.com 编辑者&#xff1a;廿瓶鲸&#xff08;和鲸社区Siby团队成员&#xff09; 3.7 边缘检测 目标 在本章中&a…

搭建jaegerAll in one 测试环境

1. Jaeger介绍 Jaeger 受 Dapper 和 OpenZipkin 的启发&#xff0c;是由 Uber Technologies 发布的开源分布式追踪系统。它用于监控和排查基于微服务的分布式系统问题&#xff0c;包括: 分布式上下文传播分布式事务监控根因分析服务依赖关系分析性能 / 延迟优化 Jaeger 架构…

初识TypeScript与静态类型解析

一、初识ts 二、如何运行ts代码 假如本地新建了一个b.ts文件 安装TypeScript&#xff1a;npm install -g typescript 编译代码&#xff1a;tsc b.ts 运行js&#xff1a;node b.js 在终端输入 tsc -init 生成 tsconfig.json 文件 类型注解&#xff1a;TypeScript里的类型注解是一…

浅学Go下的ssti

前言 作为强类型的静态语言&#xff0c;golang的安全属性从编译过程就能够避免大多数安全问题&#xff0c;一般来说也唯有依赖库和开发者自己所编写的操作漏洞&#xff0c;才有可能形成漏洞利用点&#xff0c;在本文&#xff0c;主要学习探讨一下golang的一些ssti模板注入问题…

手术麻醉信息系统源码 B/S网页版

手术麻醉信息系统源码 php mysql vue2 B/S网页版 手术麻醉信息系统是HIS产品中的一个组成部分&#xff0c;主要应用于医院的麻醉科&#xff0c;属于电子病历类产品。麻醉监护的功能覆盖整个手术与麻醉的全过程&#xff0c;包括手术申请与排班、审批、安排、术前、术中和术后…