如果所有值都不超过一个或多个字节,并且没有字节可以包含元数据,那么系统如何跟踪字节所代表的数字类型?在维基百科上看两个补语和单点揭示了这些数字如何用二进制表示,但我仍然想知道编译器或处理器(不确定我在这里真正处理的是什么)确定这个字节< em>必须是有符号整数。
这类似于收到加密的信件,看着我的密码架,想知道要抓哪个。有些指标是必要的。
如果我想一下我可以做些什么来解决这个问题,我会想到两个解决方案。我要么声称一个额外的字节并用它来存储描述,要么我会专门为数值表示分配内存部分;一个用于签名号码的部分,一个用于浮动的部分等等。
我主要处理Unix系统上的 C ,但这可能是一个更普遍的问题。
答案 0 :(得分:9)
系统如何跟踪字节所代表的数字类型?
“系统”没有。在转换期间,编译器知道它正在处理的对象的类型,并生成用于处理这些值的适当机器指令。
答案 1 :(得分:1)
事实证明,CPU 不知道字节是“已签名”还是“未签名”。因此,当您添加两个数字 - 或执行任何操作时 - 会设置“status register”标记。
看一下“标志旗”。当您添加两个数字时,CPU就会这样做 - 添加数字并将结果存储在寄存器中。但CPU说“如果我们将这些数字解释为二进制补码有符号整数,结果是负面的吗?”如果是,则将“sign flag”设置为1.
因此,如果您的程序关注已签名与未签名,在汇编中写入,您将检查该标志的状态,并且您的程序的其余部分将根据该标志执行不同的任务。
因此,当您在C中使用signed int
与unsigned 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:
@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同样可以做得很好。