【问题标题】:How does C know what type to expect?C如何知道期望什么类型?
【发布时间】:2013-02-16 06:48:39
【问题描述】:

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

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

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

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

【问题讨论】:

  • C 中的每个变量都必须有一个类型作为变量声明的一部分(可能是定义,不确定是哪个)。编译器读取类型并记住它。变量的类型并不神秘。
  • 其实只有编译器知道类型。它将您的代码编译成一个程序,其中包含操作该类型的正确指令。该类型根本不反映在生成的机器代码中。
  • @Hannesh 您的评论应该作为答案发布!
  • 是的,它比发布的任何其他内容都更中肯、更准确。

标签: c memory twos-complement single-precision


【解决方案1】:

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

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

【讨论】:

  • 那么编译器必须维护一些在编写汇编代码时使用的元数据,然后丢弃。这回答了我的问题。谢谢。
  • @JackStout:差不多,是的。大多数编译器维护所谓的符号表,其中包含有关对象类型、可见性、生命周期、链接等的信息。在翻译阶段,它用于强制执行语义规则(例如匹配分配中的类型,或阻止您修改 const-qualified 对象)。在代码生成阶段,它用于为操作选择正确的机器指令(例如,这个长字被用于浮点计算)。
【解决方案2】:

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

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

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

因此,如果您的程序关心有符号还是无符号,用汇编语言编写,您将检查该标志的状态,而您的程序的其余部分将根据该标志执行不同的任务。

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

【讨论】:

  • 如果我没记错的话,使用补码的好处是您只需对数字执行正常的加法即可。 CPU不需要关心数字是有符号/无符号/负数,它只是添加位。将值解释为正数或负数的是更高级别的代码。
  • @SamDufel 这完全正确。在这种情况下,标志提供了一种便利 - 而不是编写例程来检查数字的高位,您可以 jump 取决于该寄存器。 (嗯,还有其他技巧可以使用该位,但您确实是正确的,为此目的,该标志不是绝对必要的。)
【解决方案3】:

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

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

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

一个支持类型的程序集示例是 java VM 字节码,它具有类型后缀 对于operands for primitives

【讨论】:

    【解决方案4】:

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

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

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

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

    【讨论】:

    • C 是高级语言?真的吗?开个玩笑,C 只比宏汇编器高 3mm! ADA 是一种高级语言。
    • 是的,从技术上讲,C 是一种高级语言。很像 GLSL 是一种高级着色语言,它内置于 GPU 的汇编级别。汇编是 C 内置的目标语言,从技术上讲,机器语言或简单的二进制数据集是最低的。人们不再像我们一直认为的脚本语言那样认为 C 或 C++ 是高级别的。曾经有一段时间人们会用二进制编码。
    • @Aubin:C 与 Ada 一样“高级”;它只是没有提供像 Ada 那么多的抽象
    • 参见en.wikipedia.org/wiki/High-level_programming_language,“相对意义”一章。
    【解决方案5】:

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

    请注意,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- 中的“int”类型的允许最小值和最大值的信息3 和@5-7。 (我省略了@4 节点,因为提到的“chan”条目只是用于cha i n 生成树中的任何类型定义)

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

    如果您不相信我的话,您肯定可以自己在自己的实验中找到重复条目和 d_var 条目。

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

        .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 值同样可以做得很好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-04
      • 1970-01-01
      • 2012-08-01
      相关资源
      最近更新 更多