二进制“何时”成为十进制或十六进制?十六进制比十进制“更快”吗?

时间:2018-02-24 18:57:21

标签: c binary number-formatting cpu-architecture low-level

快速学习C语言中的命令

printf("%d", x);

printf("%lu", x);

x 的二进制表示不存在%lu %d 的模拟。

我的问题首先是,为什么会这样,其次,在什么时候 - 在哪个抽象层次 - 二进制表示会变成十进制还是十六进制?

Stack网络上的类似观点似乎只能引出语言特定的答案或实现/库建议。然而,我的问题是关于我对数据如何被抽象以及操作系统是否曾经看过二进制文件的全面理解,或者某种程度上甚至比操作系统更低级的东西都包含它。

为了进一步强调我的方向,考虑一个切线问题:用十六进制编程源文件是否比使用十进制编程(例如在常量或变量初始化期间)提供性能(速度或存储)的任何好处?

3 个答案:

答案 0 :(得分:4)

  

在哪个抽象级别 - 二进制表示变成十进制还是十六进制?

一点都没有。 CPU只能看到0和1的序列。当它们是 组合在一起,这个0和1可以具有含义,例如序列 32 0和1表示32位整数值。

我们人类不善于查看32个字符并计算我们的值 这就是为什么我们使用十进制,八进制,十六进制表示,因为 它更容易处理。标量值18是不改变的值,但是 它的表示可能会根据您拥有的位数而改变。 18英寸 二进制为0001 0010,八进制22,十六进制12,十进制18

%d的{​​{1}}和%x%o转换说明符允许我们打印标量 值分别为十进制,十六进制和otcal。 printf用于打印 无符号值。

修改

  

请说明CPU中的0和1被识别为其他任何内容......

也许你首先要理解的是小数,十六进制, ocatal,binary只是标量值的表示。我们人类使用它 表达能够掌握数量的概念。我们选择一个基数 表示修正值的数字。在十进制中,我们有10位数,0,1,2 ... 9。 每个数字都有一个固定值,当我们将这些数字组合在一起时,我们可以 表示大于9的值。例如,序列表示的值 %u等于:

3x10 0 + 2x10 1 + 1x10 2

这就是为什么我们将右边的数字称为单位列,即数字中的数字 数十列中间和左侧数字列的数字。

  

在哪一点上将0和1重写为ASCII字符或对我们更有意义的数字?

它们对CPU没有任何意义,它们只是值,模式 0和1。我们人类(或者更确切地说是创建ASCII表的身体)通过在char中说来给他们一个意义 变量的值为48,我们将其视为123,即值0 的字符表示。 CPU只看到序列 0和1,我们人类确定他们的意思,我们的算法是什么 确定我们对0和1的序列做了什么。

您不能将值与其表示混合使用。表征只对我们人类有意义。

答案 1 :(得分:0)

二进制文件intlong 与ASCII '0''1'数字字符串相同。 int是32位/ 4字节(在典型的C实现中),但每位一个字符的字符串是32字节。 ISO C没有将转换定义为基本2文本的事实与计算机内部存储整数的方式基本无关。

  

在哪个抽象级别 - 二进制表示变成十进制还是十六进制?

它不会变形printf必须计算十六进制,十进制或数字的任何基数表示的数字值。并将这些数字值转换为ASCII字符,并将它们存储在缓冲区中(或一次将它们发送到OS)。

通常的算法算法由基数重复模/除。从我对How do I print an integer in Assembly Level Programming without printf from the c library?的回答:

char *itoa_end(unsigned long val, char *p_end) {
  const unsigned base = 10;
  char *p = p_end;
  do {
    *--p = (val % base) + '0';   // for hex, also need to handle the a-f range...
    val /= base;
  } while(val);                  // runs at least once to print '0' for val=0.

  // write(1, p,  p_end-p);
  return p;  // let the caller know where the leading digit is
}

没有"魔法"在计算数字的字符串表示时,只需使用普通代码进行数学运算(编译为正常的CPU指令)。它与采用数字的任何其他函数没有什么不同,并且在{{中存储了一些字节1}}数组。

libc char[]实现将使用这样的代码将字符存储到缓冲区中。例如,glibc具有与此完全相同的内部函数,从缓冲区的末尾向后存储,从printf和其他一些函数调用。 Modulo生成base-n表示的最低有效位,但该数字在打印顺序中是最后一位。

使用变量printf的实际实现将是特殊情况基数10,基数8和基数16,因为除以编译时常量比任意情况快得多。并且已知2的幂的除/模可以编译为仅移位/ AND。但这只是一个实施细节。虽然对于2的幂基数,您可以获得打印顺序中的数字,因为它们仅取决于二进制整数中的位范围,而不是所有其他位。

  

操作系统是否曾经看过二进制文件,或者某种程度上甚至比操作系统更低级的东西都包含它。

实际上打印字符与转换为字符串表示是分开的,并且(对于base)通过fwrite(3)将使用的相同机制发生。 在被stdio缓冲后,最终printf系统调用将要求操作系统将一些字节复制到文件描述符/句柄。

大多数操作系统(包括Windows和类似POSIX的操作系统,如Linux或OS X)只有从/向文件描述符/句柄读/写字节的系统调用。 操作系统永远不会看到4字节的二进制整数,C库会在用户空间中完成所有转换。

某些CPU模拟器(如MARS或SPIM)具有"系统调用"读取用户在寄存器中键入二进制整数的字符串,反之亦然。但是正常的操作系统会将其留给用户空间库。

  

以十六进制编程源文件是否比使用十进制编程(例如,在常量或变量初始化期间)提供性能(速度或存储)的任何好处?

不,转换为二进制整数发生在编译时,因此如果源是write(),则目标文件只包含两个4字节的二进制整数,每个整数具有相同的位模式,表示相同的值。

答案 2 :(得分:0)

计算机中的数字决不会以十进制格式显示。

问题不在于它们什么时候变成二进制,而是什么时候变成十进制。

您应该能够获取二进制数0b1111011并将其转换为123十进制和0x7B十六进制,而不是使用计算器上的基本转换按钮,而是通过了解基数的转换如何工作如3785秒为1小时3分钟5秒(基数为10)。

C库看到你想要小数,它取0b1111011位到那个时刻没有任何意义它们只是位,然后它们将回到没有意义至少对他们只有意义的计算机您。要得到100的位置,你必须除以0b1100100,结果是0b1正确,所以从0b1111011减去0b1100100你得到0b10111,现在除以0b1010得到0b10所以从0b10111减去0b10100(0b1010乘以0b10)得到0b11所以到目前为止,向基数10的转换是0b1,0b10,0b11。现在printf需要生成ASCII,因此它将0b110000添加到给出0b110001,0b110010,0b110011的那些数字。并且将“字符串”提供给字符输出例程(并且您看到123)。我们在任何时候都没有任何小数,只是被操纵。

当你写一些代码时

unsigned int x = 5;

编译器将该5(源代码文件中的0b110101)转换为0b101,并将其放置在决定存储变量x的任何位置。

现在让我们回到0b1111011并将其转换为十六进制,从右边开始一次获得4位,你得到0b111和0b1011,比目前为止的基数10转换快得多(一般来说,8更快一点)位数如果是这样的话)。您可以添加0b110000的两种方法之一,然后与0b111001进行比较,或者您可以与0b1001进行比较,然后添加不同的数字。所以例如0b111变成0b110111然后检查它是否大于0b111001,nope,所以继续0b1011添加0b110000你得到0b111011是大于0b111001?是的,所以要么添加0b111或0b100111,取决于你是否想看大写字母或小写,现在字符串是例如0b110111,0b1000010加上终止零,你发送它打印,你看到输出上的7B。

十六进制输出会更快。这种收益的相关程度和相关程度取决于多种因素......

现在我不知道你用十六进制编程源文件是什么意思

unsigned int x = 0x5;

编译需要比

稍长的时间
unsigned int x = 5;

由于额外的字符。但

unsigned int x = 0x7B;

VS

unsigned int x = 123;

嗯,十进制可能还是更快。

unsigned int x = 0x11111111;

VS

unsigned int x = 286331153;

现在你不得不怀疑,在特定的机器上,十六进制的速度会更快,数据模式也很重要,如图所示。

如图所示,十六进制版本需要两个字节的存储空间来保存源文件。

unsigned int x = 0x5;

unsigned int x = 5;

编译后的输出与应用于x(0b101)的常量相同。因此,机器代码(和/或.data存储)不仅尺寸相同,而且相同。

unsigned int fun0 ( void )
{
    return(5);
}
unsigned int fun1 ( void )
{
    return(0x5);
}
unsigned int fun2 ( void )
{
    return(123);
}
unsigned int fun3 ( void )
{
    return(0x7B);
}

提供此机器代码

00000000 <fun0>:
   0:   e3a00005
   4:   e12fff1e

00000008 <fun1>:
   8:   e3a00005
   c:   e12fff1e

00000010 <fun2>:
  10:   e3a0007b
  14:   e12fff1e

00000018 <fun3>:
  18:   e3a0007b
  1c:   e12fff1e

已经存在且具有%b的C库,但它是非标准的,从未理解为什么它不是。同样是八进制,嗯,八进制有一个。

注意八进制转换与十六进制竞争,你没有条件

0b1111011,屏蔽并一次性移位3位0b001,0b111,0b011添加0x110000,因为每个都给出0b110001,0b110111,0b110011。所以你没有条件,但你有更多的“字符”要处理,因为8位数字十六进制可以赢,但更大的八进制应该赢。

关于该主题:

unsigned int fun0 ( void )
{
    return(5);
}
unsigned int fun1 ( void )
{
    return(0x5);
}
unsigned int fun2 ( void )
{
    return(05);
}
unsigned int fun3 ( void )
{
    return(123);
}
unsigned int fun4 ( void )
{
    return(0x7B);
}
unsigned int fun5 ( void )
{
    return(0173);
}

给出

00000000 <fun0>:
   0:   e3a00005
   4:   e12fff1e

00000008 <fun1>:
   8:   e3a00005
   c:   e12fff1e

00000010 <fun2>:
  10:   e3a00005
  14:   e12fff1e

00000018 <fun3>:
  18:   e3a0007b
  1c:   e12fff1e

00000020 <fun4>:
  20:   e3a0007b
  24:   e12fff1e

00000028 <fun5>:
  28:   e3a0007b
  2c:   e12fff1e

所以在源代码5的“存储”方面比05便宜比0x5便宜但是0x7B与0173相同但123更便宜。随着数字变大,十六进制变得最便宜(显然它有更高的基数16比8比10)。

你是否真的非常渴望源代码的存储空间?你需要成为一个标签人而不是空间人。并使用短变量名称和函数名称。我的长篇答案可能已经填满了你所有的公羊。