13

如果所有值都只是一个或多个字节,并且没有一个字节可以包含元数据,那么系统如何跟踪一个字节代表的数字类型?在 Wikipedia 上查看 Two's Complement 和 Single Point 揭示了这些数字如何以基数 2 表示,但我仍然想知道编译器或处理器(不确定我在这里真正处理的是哪个)确定这个字节必须是一个有符号整数。

这类似于收到一封加密的信件,然后看着我的密码书架,想知道该拿哪一个。一些指标是必要的。

如果我考虑我可以做些什么来解决这个问题,我会想到两个解决方案。要么我会要求一个额外的字节并用它来存储描述,要么我会专门为数字表示分配内存部分;有符号数的部分,浮点数的部分等。

我主要在 Unix 系统上处理 C,但这可能是一个更普遍的问题。

4

5 回答 5

9

系统如何跟踪一个字节代表的数字类型?

“系统”没有。在翻译期间,编译器知道它正在处理的对象的类型,并生成适当的机器指令来处理这些值。

于 2013-03-01T17:42:26.210 回答
1

执行的代码没有关于类型的信息。唯一知道类型的工具是编译代码时的编译器。C 中的类型只是编译时的限制,以防止您在某处使用错误的类型。在编译时,C 编译器会跟踪每个变量的类型,因此知道哪个类型属于哪个变量。

例如,这就是您需要在 中使用格式字符串的原因printfprintf由于此信息丢失,因此无法知道它将在参数列表中获得什么类型。在像 go 或 java 这样的语言中,您有一个具有反射功能的运行时,这使得获取类型成为可能。

假设您编译的 C 代码中仍有类型信息,则需要生成的汇编语言来检查类型。事实证明,在汇编中唯一接近类型的是由后缀(在 GAS 中)确定的指令的操作数大小。因此,您的类型信息中剩下的就是大小,仅此而已。

支持类型的程序集的一个示例是 java VM 字节码,它具有用于原语操作数的类型后缀。

于 2013-03-01T17:39:40.873 回答
1

重要的是要记住 C 和 C++ 是高级语言。编译器的工作是获取代码的纯文本表示并将其构建到目标平台期望执行的平台特定指令中。对于大多数使用 PC 的人来说,这往往是x86 汇编

这就是 C 和 C++ 对基本数据类型的定义如此宽松的原因。例如,大多数人说一个字节有 8 位。这不是由标准定义的,并且没有什么反对某些机器每字节 7 位作为其对数据的本机解释。该标准仅承认字节是数据的最小可寻址单元。

所以数据的解释取决于处理器的指令集。在许多现代语言中,在此之上还有另一个抽象,即虚拟机

如果您编写自己的脚本语言,则由您来定义如何在软件中解释数据。

于 2013-03-01T17:37:18.163 回答
1

哦,好问题。让我们从 CPU 开始——假设是 Intel x86 芯片。

事实证明,CPU 不知道一个字节是“有符号”还是“无符号”。因此,当您添加两个数字或进行任何操作时,会设置一个“状态寄存器”标志。

看看“标志标志”。当您添加两个数字时,CPU 会执行此操作 - 将数字相加并将结果存储在寄存器中。但是 CPU 说“如果我们将这些数字解释为二进制补码有符号整数,结果是否为负?” 如果是这样,则该“标志标志”设置为 1。

因此,如果您的程序关心有符号与无符号,在汇编中编写,您将检查该标志的状态,并且您的程序的其余部分将根据该标志执行不同的任务。

因此,当您在 C 中使用signed intvsunsigned int时,您基本上是在告诉编译器如何(或是否)使用该符号标志。

于 2013-03-01T17:28:01.080 回答
0

除了编译器使用C 语言,它完全知道给定值的类型,没有系统知道给定值的类型。

请注意,C 本身并没有带来任何运行时类型的信息系统。

看看下面的例子:

int i_var;
double d_var;

int main () {

  i_var = -23;
  d_var = 0.1;

  return 0;
}

在代码中,涉及两种不同类型的值,一种存储为整数,另一种存储为双精度值。

分析代码的编译器非常了解它们的确切类型。这里是 gcc 在生成通过传递-fdump-tree-all给 gcc 生成的代码时保存的类型信息的短片段的转储:

@1      type_decl        name: @2       type: @3       srcp: <built-in>:0      
                         chan: @4      
@2      identifier_node  strg: int      lngt: 3       
@3      integer_type     name: @1       size: @5       algn: 32      
                         prec: 32       sign: signed   min : @6      
                         max : @7      
...
@5      integer_cst      type: @11      low : 32      
@6      integer_cst      type: @3       high: -1       low : -2147483648 
@7      integer_cst      type: @3       low : 2147483647 
...

@3805   var_decl         name: @3810    type: @3       srcp: main.c:3      
                         chan: @3811    size: @5       algn: 32      
                         used: 1       
...
@3810   identifier_node  strg: i_var    lngt: 5    

寻找@links,您应该清楚地看到,确实存储了很多关于内存大小、对齐约束以及存储在节点@1-3 和@ 中的“int”类型的允许最小值和最大值的信息5-7。(我省略了@4 节点,因为提到的“ chan ”条目仅用于链接生成树中的任何类型定义)

关于在 main.c 第 3 行声明的变量,众所周知,它持有一个 int 类型的值,正如对节点 @3 的类型引用所见。

如果您不相信我,您肯定可以自己在自己的实验中找到双重条目和 d_var 的条目,他们也会在那里。

看看-S列出的生成的汇编代码(使用 gcc 传递开关),我们可以看看编译器在代码生成中使用这些信息的方式:

    .file   "main.c"
    .comm   i_var,4,4
    .comm   d_var,8,8
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $-23, i_var
    fldl    .LC0
    fstpl   d_var
    movl    $0, %eax
    popl    %ebp
    ret
    .size   main, .-main
    .section    .rodata
    .align 8
.LC0:
    .long   -1717986918
    .long   1069128089
    .ident  "GCC: (Debian 4.4.5-8) 4.4.5"
    .section    .note.GNU-stack,"",@progbits

查看赋值指令,您会发现编译器找到了正确的指令“mov”来分配我们的 int 值和“fstp”来分配我们的“double”值。

然而,除了在机器级别选择的指令之外,没有指示这些值的类型。查看存储在 .LC0 中的值,值 0.1 的类型“double”甚至在两个连续的存储位置中分解为 long,以满足汇编器的已知“类型”。

事实上,以这种方式分解值只是其他可能性的一种选择,使用 8 个连续的 "type" .byte 值也会做得同样好。

于 2013-03-01T18:21:58.837 回答