PC_数据的存储和排列+C语言中的基本类型转换规律+输入输出控制符(scanf/printf)
文章目录
PC_数据的存储和排列+C语言中的基本类型转换规律+输入输出控制符(scanf/printf)
Ref
- K&R(p235)
存储单元的地址分配
-
主存各
存储单元
的空间位置
是由单元地址号
来表示的, -
而
地址总线
是用来指出存储单元地址号
的,根据该地址可读出或写人一个存储字
-
不同的机器
存储字长
也不同,为了满足字符处理的需要,常用8位二进制数表示一个字节
,因此存储字长都取8(bit)的倍数 -
通常计算机系统既可
按字
寻址,也可按字节
寻址-
例如IBM 370机器(记为A机器)的
字长为32位
,-
它可按
字节
寻址,即它的每一个存储字包含4个可独立寻址的字节
, -
其地址分配如图(a)所示
-
字节地址 Note 字
地
址0 1 2 3 w1 4 5 6 7 w2 8 9 10 11 w3 IBM370机器 -
字地址(word address)
的二进制分别是:0000,0100,1000 -
该矩阵下,每一行代表该机器的一个字(word);
- 行内的四个数字分别表示该字所包含的四个字节的四个字节地址
-
-
字地址是用(构成)该
字
(的4个字节中的)高位字节的地址来表示 -
故其
字地址
是4(4Byte)整数倍,正好用地址码
的末两位
来区分同一字
的4个字节的位置
-
对
24位地址线
的主存而言,按字节
寻址的范围是16 M( 2 24 = 2 4 × 2 20 = 16 M ( B y t e ) 2^{24}=2^{4}\times 2^{20}=16M(Byte) 224=24×220=16M(Byte),- 地址线的位数直接决定了字节的寻址能力(范围,和机器相关的单位是
字
,也就是同样位数的地址线的寻址范围会因为不同的机器而不同); - 按
字
寻址的范围为4M(按字节寻址的范围宽度/存储字长= 2 24 / 4 = 4 ⋅ 2 20 = 4 M 2^{24}/4=4\cdot{2^{20}}=4M 224/4=4⋅220=4M)- 机器A的一个字包含4个Byte
- 地址线的位数直接决定了字节的寻址能力(范围,和机器相关的单位是
-
-
但对PDP -11机(记为B机器)而言,其
字长为16位
,字地址是2(Byte)的整数倍.-
它用
低位字节
的地址来表示字地址
-
字节地址 Note 字
地
址0 1 w1 2 3 w2 4 5 w3 PDP-11
-
- 对
24位地址线
而言,按字节
寻址的范围仍为16 M- 但按
字
寻址的范围为8 M
- 但按
-
-
数据的存储和排列
- 现代计算机基本上都采用
字节编址
,即每个地址编号
中存放1字节 - 不同类型的数据占用的
字节数
不同,- int和 float型数据占4字节,double型数据占8字节等,而
程序中对每个数据只给定一个地址
- 假设
变量i
的地址为08 00H,字节
01H、23H、45H、67H应该各有一个内存地址
(字节地址) - 那么地址08 00H对应4字节中
哪字节的地址(最高字节地址还是最低字节地址)
呢?
- int和 float型数据占4字节,double型数据占8字节等,而
字节序
-
现代的计算机系统一般采用字节(Octet, 8 bit Byte)作为逻辑寻址单位
-
当物理单位的长度大于1个字节时,就要区分字节序(Endianness),字节序是指处理器在处理多字节数据时,在寄存器和内存中保存字节的顺序
-
为了简便起见它的英文也常常表示为 Byte Order
-
目前在各种体系的计算机中通常采用的字节存储机制主要有两种:
- Little-Endian
- Big-Endian
- 另外还有一种不常用的字节序Middle-Endian,又称为Mixed-Endian或者PDP-Endian,它是Little-Endian 和 Big-Endian的混合体
-
Big-Endian(BE),是指数据的高字节(MSB)保存在内存的低地址中,而数据的低字节(LSB)保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;
-
Little-Endian(LE),是指数据的高字节(MSB)保存在内存的高地址中,而数据的低字节(LSB)保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致
MSB最高有效字节
- MSB(Most Significant Byte),最高有效字节,指多字节序列中具有最大权重的字节,是一个数据中权值最大的那一个字节
LSB最低有效字节
- LSB(Least Significant Byte),最低有效字节,指多字节序列中最小权重的字节,是一个数据中权值最小的那一个字节
大/小端存放方式
大端方式
- 从数据的最高有效字节到最低有效字节的顺序存储数据,即
最高有效字节存放在前面
;
小端方式
- 按从最低有效字节到最高有效字节的顺序存储数据,即
最低有效字节存放在前面
例子
-
保存十六进制数据01234567H(相当于32位二进制数)
-
>>> bin(0x01234567) '0b1001000110100010101100111'
-
内存地址 | … | 0800H | 0801H | 0802H | 0803H | … |
---|---|---|---|---|---|---|
大端方式 | … | 01H | 23H | 45H | 67H | … |
小端方式 | … | 67H | 45H | 23H | 01H | … |
- 通常的,一个数的书写顺序是从高位写到低位
- 而保存顺序却有两种,从的低位开始保存(小端方式)或者从高位开始保存(大端方式)
- 比如01H;23H;45H;67H
- M S B = 01 H MSB=01H MSB=01H(最高权重字节)
- L S B = 67 H LSB=67H LSB=67H(最低权重字节)
- 内存的地址从低地址到高地址增长
-
大端方式是先存放数据的高位字节再存放低位字节
- 例如1122H,高位字节11H先被存放在低地址内存单元处
-
小端方式先存放的是数据的低位字节
- 例如,1122H,低位字节22H先被存放在低地址内存单元
-
应用情况
- 我们常见的大部分处理器都采用Little-Endian,
- 例如x86、6502、Z80、VAX以及PDP-11等;
- 使用Big-Endian的处理器通常是Motorola的处理器,
- 例如:6800、68000、PowerPC 以及System/370等;
- 可切换的字节序(Bi-Endian)
- Middle-Endian 使用很少,偶尔会在一些小型机体系中的十进制数的压缩格式中出现
按边界对齐存储
- 按照边界对齐存储,可提高访存速度(但是牺牲了一部分空间,但是这往往是值得的)
- 按边界对齐的基本原理是,计算机每次访存,可以读取一个存储字(或者更少的内容,但是无法多个字)
- 设机器字长为32bit;
- 如果某个数据X总共占用了一个字(比如32bit),这个数据恰好存放在某一个字 W 0 W_0 W0(从该字的最低字节到最高字节全部用来存放X),这种情况下可以一次性取出
- 但是如果内存中的字 W 0 W_0 W0已经其他某个数据占用了前两个字节,那么如果要继续利用 W 0 W_0 W0来存放X,会出现只能够存一般的数据,还有两个字节需要存到外的字中(比如 W 1 W_1 W1),这就导致一次方寸无法完整取出X,需要访存两次
- 因此,如果按照边界兑取存储(数据应该尽可能的不要跨字节存储,那么访问起来就高效)
- (而不是说数据必须要存在字的边缘字节)
C语言中的基本类型转换规律
- 记住,所有整数类型都是以补码的形式存储整数:
- 存在无论是正整数,负整数,无符号数,在讨论相关问题前都需要考虑补码形式
- 尤其是负数
实例代码
#include <stdio.h> void print_seperator(void) { printf("\n------------------------\n"); } void s2u() { /* 探究无符号数和有符号数之间的转换 */ printf("给定2个真值x,y\ny是x的强制类型转换为无符号数后的对应值"); short x = -4321; unsigned short y = (unsigned short)x; /* 逆向转换实例 */ unsigned short p = 65535; short q = (short)p; printf("sizeof short int:%d\n", sizeof(x)); printf("x(%%d)=%d,y(%%u)=%u\n", x, y); print_seperator(); printf("\n观察寄存器中的bit情况,一般情况下,您会发现,整形变量中的二进制串是以补码的形式存在的\n"); printf("@@check the bits value of variable x,y in the registor\n with 16(hexadecimal form)\n"); printf("x(%%hx)=%hx,y(%%hx)=%hx\n", x, y); print_seperator(); printf("可以看到,强制转换为无符号数后得到的y,其二进制编码和有符号数x的二进制(补码)串是相同的,为了便于简写,使用16进制来标记,每个16进制数尾缀用H标识,不属于数据位!);\n x,y在计算或者以整形%d输出的时候,会转回换为真值输出:\n"); printf("有符号数x=-4321的十六进制补码是ef1fH,(附|x|=4321的十六进制为10e1H,计算x的补码确实是ef1fH\n"); printf("输出无符号数y的时候,就是将ef1fH按照无符号数%%d(%%u)解释方式输出(对应的无符号数y的真值为61215)\n"); print_seperator(); printf("强制类型转换的结果位值保持不变;仅改变了解释这些位的方式!"); // printf("x(%%x)=%x,y(%%x)=%x\n", x, y); print_seperator(); printf("x(%%u)=%u,y(%%d)=%d\n", x, y); printf("p(%%hx)=%hX,q(%%hx)=%hx\n", p, q); printf("p(%%h)=%d,q(%%h)=%hd\n", p, q); } void l2s() { /* 探究不同字长之间的数的类型转换 本例以int转换为short查看效果 */ printf("给定2个真值u1,u2\n"); int u1 = 165537, u2 = -34991; short v1 = (short)u1, v2 = (short)u2; /* 逆向转换实例 */ printf("sizeof short int:%d\n", sizeof(u1)); print_seperator(); printf("u1(%%d)=%d,u1(%%d)=%d\n", u1, u2); printf("再以十六进制的形式输出,最高位开始的0bit默认不显示因此32bit的十六进制数未必有8个值\n"); printf("u1(%%x)=%#x,u2(%%x)=%#x\n", u1, u2); print_seperator(); printf("\n观察寄存器中的bit情况,一般情况下,您会发现,整形变量中的二进制串是以补码的形式存在的\n"); printf("@@check the bits value of variable u1,u2 in the registor\n with 16(hexadecimal form)\n"); printf("下面两种打印方式结果一致(可以通过输出控制符来直接截断高位字节!(只输出低位字节);此外,以十六进制输出时,可以在控制字符序列中添加一个#号,打印的时候自动添加头部的Ox(或者OX)\n"); printf("u1(%%hx)=%hx,u2(%%hx)=%hx\n", u1, u2); printf("v1(%%hx)=%#hx,v2(%%hx)=%#hx\n", v1, v2); print_seperator(); /* 尝试用用int 打印short的变量的补位效果 */ printf("v1(%%hx)=%#hx,v2(%%hx)=%#hx\n", v1, v2); printf("v1(%%x)=%#x,v2(%%x)=%#x\n", v1, v2); printf("v1(%%d)=%#d,v2(%%dx)=%#d\n", v1, v2); print_seperator(); // printf("x(%%u)=%u,y(%%d)=%d\n", x, y); // printf("p(%%hx)=%hX,q(%%hx)=%hx\n", p, q); // printf("p(%%h)=%d,q(%%h)=%hd\n", ); } void s2l() { /* 探究不同字长之间的数的类型转换 本例以int转换为short查看效果 */ printf("给定2个真值u1,u2\n"); short u0 = 4321; short u1 = -4321; unsigned short u2 = (unsigned short)u1; /* 有符号数的short向int转换 */ int v1 = (int)u1; /* 无符号数的short转int */ unsigned int v2 = (unsigned int)u2; /* 逆向转换实例 */ print_seperator(); // printf("u1(%%d)=%d,u1(%%d)=%d\n", u1, u2); printf("再以十六进制的形式输出,最高位开始的0bit默认不显示因此32bit的十六进制数未必有8个值\n"); printf("u0(%%hx)=%#hx,(int)u0(%%x)=%#x\n", u0, (int)u0); printf("u1(%%hx)=%#hx,v1(%%x)=(int)u1=%#x,u2(%%x)=%#x,v2(%%x)=(unsigned int)u2=%#x\n", u1, v1, u2, v2); printf("u1(%%d)=%#d,v1(%%d)=%#d,u2(%%d)=%#d,v2(%%d)=%#d\n", u1, v1, u2, v2); print_seperator(); } int main(int argc, char const *argv[]) { printf("section1:short转unsigned short🎈🎈🎈🎈\n"); s2u(); printf("section2:int转short:🎈🎈🎈🎈\n"); l2s(); printf("section3:short转int:🎈🎈🎈🎈\n"); s2l(); return 0; }
输出结果
- 下面程序使用mingw(gcc)编译
- 直接使用powershell可能会乱码运行输出的乱码问题
- 使用oh my posh 不会中文乱码
- 直接powershell输出可能会乱码
- 使用cmd运行也是可以的(无乱码)
section1:short转unsigned short🎈🎈🎈🎈 给定2个真值x,y y是x的强制类型转换为无符号数后的对应值sizeof short int:2 x(%d)=-4321,y(%u)=61215 ------------------------ 观察寄存器中的bit情况,一般情况下,您会发现,整形变量中的二进制串是以补码的形式存 在的 @@check the bits value of variable x,y in the registor with 16(hexadecimal form) x(%hx)=ef1f,y(%hx)=ef1f ------------------------ 可以看到,强制转换为无符号数后得到的y,其二进制编码和有符号数x的二进制(补码)串是 相同的,为了便于简写,使用16进制来标记,每个16进制数尾缀用H标识,不属于数据位!); x,y在计算或者以整形-2074805712输出的时候,会转回换为真值输出: 有符号数x=-4321的十六进制补码是ef1fH,(附|x|=4321的十六进制为10e1H,计算x的补码确 实是ef1fH 输出无符号数y的时候,就是将ef1fH按照无符号数%d(%u)解释方式输出(对应的无符号数y的 真值为61215) ------------------------ 强制类型转换的结果位值保持不变;仅改变了解释这些位的方式! ------------------------ x(%u)=4294962975,y(%d)=61215 p(%hx)=FFFF,q(%hx)=ffff p(%h)=65535,q(%h)=-1 section2:int转short:🎈🎈🎈🎈 给定2个真值u1,u2 sizeof short int:4 ------------------------ u1(%d)=165537,u1(%d)=-34991 再以十六进制的形式输出,最高位开始的0bit默认不显示因此32bit的十六进制数未必有8个 值 u1(%x)=0x286a1,u2(%x)=0xffff7751 ------------------------ 观察寄存器中的bit情况,一般情况下,您会发现,整形变量中的二进制串是以补码的形式存 在的 @@check the bits value of variable u1,u2 in the registor with 16(hexadecimal form) 下面两种打印方式结果一致(可以通过输出控制符来直接截断高位字节!(只输出低位字节);此外,以十六进制输出时,可以在控制字符序列中添加一个#号,打印的时候自动添加头部的Ox(或者OX) u1(%hx)=86a1,u2(%hx)=7751 v1(%hx)=0x86a1,v2(%hx)=0x7751 ------------------------ v1(%hx)=0x86a1,v2(%hx)=0x7751 v1(%x)=0xffff86a1,v2(%x)=0x7751 v1(%d)=-31071,v2(%dx)=30545 ------------------------ section3:short转int:🎈🎈🎈🎈 给定2个真值u1,u2 ------------------------ 再以十六进制的形式输出,最高位开始的0bit默认不显示因此32bit的十六进制数未必有8个 值 u0(%hx)=0x10e1,(int)u0(%x)=0x10e1 u1(%hx)=0xef1f,v1(%x)=(int)u1=0xffffef1f,u2(%x)=0xef1f,v2(%x)=(unsigned int)u2=0xef1f u1(%d)=-4321,v1(%d)=-4321,u2(%d)=61215,v2(%d)=61215 ------------------------
小结
- 有符号数和无符号数的相互转换,转换前后,它们的二进制补码(记这串01串为T)都是一致的
- 输出的数有区别,在于解释同一串二进制代码是的解释方式不同
- 有符号数按照正常的补码解释串T,得到正确的(原本的)真值
- 如果一无符号数来解释T,会得到其他结果
- 有符号数和无符号数在取值范围上就不同
- 长字长类型转换为短字长类型:
- 比如int转为short,那么机器内部的补码形式中高位字节被丢弃
- 短字长转换为长字长类型
- 那么C语言会试图保持转换前后,数的真值不发生变化
- 因此,对于正数或者无符号数,长字长类型的高位将用0填补(补码)
- 对于负数,则用1来填补
- 那么C语言会试图保持转换前后,数的真值不发生变化
C语言输入输出格式控制
scanf
-
scanf 函数处理格式化输入转换
-
int fscanf(FILE *stream, const char *format, ...)
-
fscanf函数根据格式串 format
- (从流 stream中读取输入,并把转换后的值赋值给后续各个参数,其中的每个参数都必须是一个指针
- 当格式串 format用完时,函数返回。
- 如果到达文件的末尾或在转换输入前出错,该函数返回EOF,
- 否则,(返回实际被转换并赋值的输入项的数目。
-
格式串 format通常包括转换说明,它用于指导对输入进行解释。
-
格式字符串中可以包含下列项目:
- 空格或制表符
- 普通字符(&除外),它将与输入流中下一个非空白字符进行匹配
- 转换说明,由一个&,一个赋值屏蔽字符(可选
*
,一个指定最大字段宽度的数(可选)、一个指定目标字段宽度的字符(h、l或z)(可选)以及一个转换字符组成。- 转换说明决定了下一个输入字段的转换方式。
- 通常结果将被保存在由对应参数指向的变量中。
- 但是,如果转换说明中包含赋值屏蔽字符
*
,例如%*s
,则将跳过对应的输入字段,并不进行赋值。 - 输入字段时,1个由非空白符字符组成的字符串,当遇到下一个空白符或达到最大字段宽度(如果有的话)时,对当前输入字段的读取结束。
- 这意味着,scanf 函数可以跨越行的边界读取输入,因为换行符也是空白符(空白符包括空格、横向制表符、纵向制表符、换行符、回车符和换页符)。
- 转换字符说明了对输入字段的解释方式,对应的参数必须是指针。
-
合法的转换字符如表
- 如果参数是指向short类型而非int类型的指针,则在转换字符
d、i、n、o、u和x
之前可以加上前缀h
。 - 如果参数是指向long类型的指针,则在这几个转换字符前可以加上字母
l
。 - 如果参数是指向double类型而非float类型的指针,则在转换字符
e、f和g
前可以加上字母l
。 - 如果参数是指向long double类型的指针,则在转换字符
e、f和g
前可以加上字母L。
- 如果参数是指向short类型而非int类型的指针,则在转换字符
prinft
- printf函数提供格式化输出转换。
int fprintf(FILE *stream, const char *format, ...)
- fprintf函数按照format说明的格式对输出进行转换,并写到stream流中。
- 返回值是实际写入的字符数。
- 若出错则返回一个负值。
- 格式串由两种类型的对象组成:
- 普通字符(将被复制到输出流中)
- 转换说明(分别决定下一后续参数的转换和打印)。
- 每个转换说明均以字符%开头,以转换字符结束。
- 在%与转换字符之间可以依次包括下列内容:
- 标志(可以以任意顺序出现),用于修改转换说明
-
指定被转换的参数在其字段内左对齐+
指定在输出的数前面加上正负号空格
如果第一个字符不是正负号,则在其前面加上一个空格0
对于数值转换,当输出长度小于字段宽度时,添加前导О进行填充#
指定另一种输出形式。- 如果为o转换,则第一个数字为零;
- 如果为x或x转换,则指定在输出的非О值前加0x或ox;
- 对于e、E、f、g或G转换,指定输出总包括一个小数点;
- 对于g或c转换,指定输出值尾部无意义的0将被保留
一个数值
- 用于指定最小字段宽度。
- 转换后的参数输出宽度至少要达到这个数值。
- 如果参数的字符数小于此数值,则在参数宽边(如果要求左对齐的话则为右边)填充一些字符。填充字符通常为空格,但是,如果设置了О填充标志,则填充字符为0。
点号
,用于分隔字段宽度和精度。表示精度的数
- 对于字符串,它指定打印的字符的最大个数;
- 对于e、E或f转换,它指定打印的小数点后的数字位数;
- 对于g或c转换,它指定打印的有效数字位数;
- 对于整型数,它指定打印的数字位数(必要时可加填充位О以达到要求的宽度)。
长度修饰符h、l或L
。- h表示将相应的参数按short或unsigned short类型输出。
- l表示将相应的参数按long或unsigned long类型输出;
- L表示将相应的参数按long double类型输出。
- 宽度和精度中的任何一个或两者都可以用
*
指定,- 这种情况下,该值将通过转换下一个参数计算得到(下一个参数必须为int类型)。
- 下表列出了这些转换字符及其意义。
- 如果%后面的字符不是转换字符,则其行为没有
定义。
- 如果%后面的字符不是转换字符,则其行为没有
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2021-11-08 powershell_配置文件与脚本编写(模板字符串插值/运算符与表达式/函数参数/控制流)(by offical)
2021-11-08 powershell@别名@命令异常排查@对象属性查询
2021-11-08 git_gitignore的配置(exclude&include)/只跟踪指定目录下的指定文件