PC_数据的存储和排列+C语言中的基本类型转换规律+输入输出控制符(scanf/printf)

PC_数据的存储和排列+C语言中的基本类型转换规律+输入输出控制符(scanf/printf)

Ref

  • K&R(p235)

存储单元的地址分配

  • 主存各存储单元空间位置是由单元地址号来表示的,

  • 地址总线是用来指出存储单元地址号的,根据该地址可读出或写人一个存储字

  • 不同的机器存储字长也不同,为了满足字符处理的需要,常用8位二进制数表示一个字节,因此存储字长都取8(bit)的倍数

  • 通常计算机系统既可按字寻址,也可按字节寻址

    • 例如IBM 370机器(记为A机器)的字长为32位,

      • 它可按字节寻址,即它的每一个存储字包含4个可独立寻址的字节,

      • 其地址分配如图(a)所示

        • 字节地址Note


          0123w1
          4567w2
          891011w3
          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=4220=4M)
          • 机器A的一个字包含4个Byte
    • 但对PDP -11机(记为B机器)而言,其字长为16位,字地址是2(Byte)的整数倍.

      • 它用低位字节的地址来表示字地址

        • 字节地址Note


          01w1
          23w2
          45w3
          PDP-11
      • 24位地址线而言,按字节寻址的范围仍为16 M
        • 但按寻址的范围为8 M

数据的存储和排列

  • 现代计算机基本上都采用字节编址,即每个地址编号中存放1字节
  • 不同类型的数据占用的字节数不同,
    • int和 float型数据占4字节,double型数据占8字节等,而程序中对每个数据只给定一个地址
    • 假设变量i的地址为08 00H,字节01H、23H、45H、67H应该各有一个内存地址(字节地址)
    • 那么地址08 00H对应4字节中哪字节的地址(最高字节地址还是最低字节地址)呢?

字节序

  • 现代的计算机系统一般采用字节(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'
内存地址0800H0801H0802H0803H
大端方式01H23H45H67H
小端方式67H45H23H01H
  • 通常的,一个数的书写顺序是从高位写到低位
    • 保存顺序却有两种,从的低位开始保存(小端方式)或者从高位开始保存(大端方式)
    • 比如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)
    • 像ARM、PowerPC、Alpha、SPARC V9、MIPS、PA-RISC和IA64等体系结构 支持可切换的字节序
    • 这个特性可以提高效率或者简化网络设备和软件的逻辑
    • 这种可切换的字节序被称为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语言输入输出格式控制

scanf

  • https://cplusplus.com/reference/cstdio/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。
    • 在这里插入图片描述
      在这里插入图片描述

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类型)。
  • 下表列出了这些转换字符及其意义。
    • 如果%后面的字符不是转换字符,则其行为没有
      定义。

在这里插入图片描述

posted @   xuchaoxin1375  阅读(11)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享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)/只跟踪指定目录下的指定文件
点击右上角即可分享
微信分享提示