C如何知道预期的类型?

时间:2013-03-01 17:21:17

标签: c memory twos-complement single-precision

如果所有值都不超过一个或多个字节,并且没有字节可以包含元数据,那么系统如何跟踪字节所代表的数字类型?在维基百科上看两个补语和单点揭示了这些数字如何用二进制表示,但我仍然想知道编译器或处理器(不确定我在这里真正处理的是什么)确定这个字节< em>必须是有符号整数。

这类似于收到加密的信件,看着我的密码架,想知道要抓哪个。有些指标是必要的。

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

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

5 个答案:

答案 0 :(得分:9)

  

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

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

答案 1 :(得分:1)

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

事实证明,CPU 不知道字节是“已签名”还是“未签名”。因此,当您添加两个数字 - 或执行任何操作时 - 会设置“status register”标记。

看一下“标志旗”。当您添加两个数字时,CPU就会这样做 - 添加数字并将结果存储在寄存器中。但CPU说“如果我们将这些数字解释为二进制补码有符号整数,结果是负面的吗?”如果是,则将“sign flag”设置为1.

因此,如果您的程序关注已签名与未签名,在汇编中写入,您将检查该标志的状态,并且您的程序的其余部分将根据该标志执行不同的任务。

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

答案 2 :(得分:1)

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

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

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

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

答案 3 :(得分:1)

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

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

假设您编译的C代码仍然包含类型信息,则需要 生成的汇编语言来检查类型。事实证明,组装中唯一接近类型的是尺寸 由suffixes (in GAS)确定的指令的操作数。因此,您的类型信息还剩下的是尺寸,仅此而已。

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

答案 4 :(得分:0)

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

请注意,C本身不带任何运行时类型信息系统。

看看下面的例子:

int i_var;
double d_var;

int main () {

  i_var = -23;
  d_var = 0.1;

  return 0;
}

在代码中有两种不同类型的值,一种存储为整数,另一种存储为double值。

很好地分析代码的编译器知道它们的确切类型。这里通过将-fdump-tree-all传递给gcc:

生成代码生成的代码信息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”类型甚至在两个连续的存储位置中被分解,每个存储位置都很长,以满足汇编程序的已知“类型”。

事实上,以这种方式突破价值只是其他可能性的一种选择,使用8个连续的“类型”值.byte同样可以做得很好。