什么时候数据类型对编译器的意义大于其存储 space?
When does a data type means more than its storage space to the compiler?
假设我有以下变量声明:
float f = 23.4;
现在这意味着两件事(也许更多?):
- 它告诉编译器在内存中分配 4 个字节的存储空间。
- 它告诉编译器将这 4 个字节存储中包含的位视为实数。
现在我的问题是 f
什么时候被编译器视为实数,而不仅仅是 4 字节的存储 space?我可以想到以下场景:
- 当我给它赋一个数字(例如23.4)时,编译器会将这个数字转换成代表实数的相应位,然后将其放入
f
.
- 当我为它分配一个
int
变量时,int
变量将被转换(转换)为代表实数的位并放入 f
(即使意味着数据丢失)。或者当我试图给它分配一个不允许的数据类型时(例如 struct
),编译器会抱怨。
- 当我将它用作算术运算中的操作数时,例如
i + f
,编译器会知道f
是一个float
而i
是一个int
等不只是将 f
和 i
中包含的位相加,而是将这些位表示的内容相加。
但是当我想输出 f
到控制台时,编译器与它无关。因此,当我使用 printf()
时,是我告诉 printf()
f
代表什么(通过指定 %f
参数)。或者当我想从控制台读取输入时,也是我告诉 scanf()
我想将从用户接收到的字节转换为实数(通过指定 %f
参数)。
那么还有没有其他场景由编译器负责解释变量的内容?
请检查http://en.wikipedia.org/wiki/Double-precision_floating-point_format
它比 4 个字节的存储多一点。
整个CPU浮点运算基于编译器生成正确的指令集并了解数据类型,
当然你可以分配 4 个字节的存储空间,伪装成实数 (cast),但这无异于搬起石头砸自己的脚。
您最终要问的是编译器如何工作以及它们如何处理类型信息和数据表示。这里的答案很广泛,有多种方法:
一些编译器执行类型擦除,因此当特定变量在内存中时,没有关于它是什么类型的信息。当您调用 printf
时,编译器如何知道它应该如何处理数据?好吧,因为你告诉它类型。表现出类型擦除的编译器确保对于任何变量,所有类型在编译时都是已知的,因此当将更高级别的程序翻译成机器语言时,比如将两个变量加在一起,它知道是执行整数加法还是浮动点加法,因为它知道所涉及变量的类型。它还可以知道应该如何传递要打印的变量等。
另一方面,一些解释器和托管编译器不展示类型擦除。相反,这些编译器将表示变量类型的代码编码为变量本身的一部分。这允许解释器在运行时检查变量是什么,必要时转换它,并决定对其执行什么操作。
请注意,整数和单精度浮点数在内存中的表示方式非常不同(尽管在大多数语言和体系结构中都占用 4 个字节)。要知道如何显示或添加某些东西,编译器需要知道它是什么类型。编译器可以发出决定在运行时(一些托管程序)的代码,或者解释器可以决定在运行时如何处理它,或者编译器可能需要知道每个变量在编译时是什么,从而消除了需要在运行时在内存中存储和指定类型。
在 C 中,printf
是该语言表明它不是完全类型安全的领域之一(最初让我感到困惑的是,为什么有人会说 C 不是类型安全的,因为它显然有类型和似乎 强类型)。
请注意,在 C++ 中,使用 std::cout
,您可以输出 int
、double
、std::string
等,而无需告诉编译器您正在输出什么。这是因为对于 std::cout
,C++ 编译器将在编译时找出变量的类型并调用适当的 <<
重载,这会正确格式化表示变量的内存块,或者作为int
、一个 double
、一个 std::string
,或者随便什么。
然而,对于 printf
,该函数的规范旨在接受一堆 "data" 并打印它。我敢肯定,出现这种情况有一些有趣的历史原因。最明显的一个是可变长度参数不能用多种类型描述,因此传递给 printf 的参数是某种 "any" 类型..但是在编译函数体时,编译器知道传递的参数数量可变,但不知道每个参数的类型。
如果您为 printf
提供了数十个重载,其中包括不同可能类型的每种组合和排列,直到一定长度,那么您就可以避免向程序提示类型在运行时。
严格来说,我不认为这是对你问题的回答,而是听起来你误会了什么。
首先,您的假设有几个错误:
-
float
变量的声明不会告诉编译器 "allocate 4 bytes of storage in memory"。它告诉编译器分配一个 float
,但它是在内存中分配,在寄存器中,完全隐式分配(没有 real 存储),或者可以想象,在某些完全其他方式取决于编译器。对于所有 C 规范问题,编译器可以使用您的声卡将 float
存储在扬声器和麦克风之间的音频延迟循环中。
- 即使您将
float
传递给 printf()
,编译器也会参与其中。您可能认为它只是将四个字节复制到堆栈上,但这只是 x86 ABI 的巧合。即使在 AMD64 上,这也是不正确的,可变参数函数的 float
参数将在 SSE 寄存器中传递(与在整数 GPR 中传递的 int
参数相反,因此编译器非常大有不同)。
当然,编译器确实没有告诉 printf()
去获取一个 float
来打印它,但是这个责任在你身上,但这不是因为编译器"ignores" 您的 float
变量的内容,而只是因为没有传递给函数的运行时类型信息,因此 printf()
需要有关从调用框架中获取什么样的值的信息.或者,换句话说,即使编译器知道值的数据类型并相应地调整生成的代码以匹配 ABI 和所有内容,它也没有义务告诉 printf()
它传递给它的是什么,因此您需要在原处告诉 printf()
它。
从这个角度来看,要回答您的问题,数据类型对编译器的意义永远不仅仅是它的存储大小,但我觉得您想问的实际问题是另外一回事。
假设我有以下变量声明:
float f = 23.4;
现在这意味着两件事(也许更多?):
- 它告诉编译器在内存中分配 4 个字节的存储空间。
- 它告诉编译器将这 4 个字节存储中包含的位视为实数。
现在我的问题是 f
什么时候被编译器视为实数,而不仅仅是 4 字节的存储 space?我可以想到以下场景:
- 当我给它赋一个数字(例如23.4)时,编译器会将这个数字转换成代表实数的相应位,然后将其放入
f
. - 当我为它分配一个
int
变量时,int
变量将被转换(转换)为代表实数的位并放入f
(即使意味着数据丢失)。或者当我试图给它分配一个不允许的数据类型时(例如struct
),编译器会抱怨。 - 当我将它用作算术运算中的操作数时,例如
i + f
,编译器会知道f
是一个float
而i
是一个int
等不只是将f
和i
中包含的位相加,而是将这些位表示的内容相加。
但是当我想输出 f
到控制台时,编译器与它无关。因此,当我使用 printf()
时,是我告诉 printf()
f
代表什么(通过指定 %f
参数)。或者当我想从控制台读取输入时,也是我告诉 scanf()
我想将从用户接收到的字节转换为实数(通过指定 %f
参数)。
那么还有没有其他场景由编译器负责解释变量的内容?
请检查http://en.wikipedia.org/wiki/Double-precision_floating-point_format
它比 4 个字节的存储多一点。
整个CPU浮点运算基于编译器生成正确的指令集并了解数据类型,
当然你可以分配 4 个字节的存储空间,伪装成实数 (cast),但这无异于搬起石头砸自己的脚。
您最终要问的是编译器如何工作以及它们如何处理类型信息和数据表示。这里的答案很广泛,有多种方法:
一些编译器执行类型擦除,因此当特定变量在内存中时,没有关于它是什么类型的信息。当您调用 printf
时,编译器如何知道它应该如何处理数据?好吧,因为你告诉它类型。表现出类型擦除的编译器确保对于任何变量,所有类型在编译时都是已知的,因此当将更高级别的程序翻译成机器语言时,比如将两个变量加在一起,它知道是执行整数加法还是浮动点加法,因为它知道所涉及变量的类型。它还可以知道应该如何传递要打印的变量等。
另一方面,一些解释器和托管编译器不展示类型擦除。相反,这些编译器将表示变量类型的代码编码为变量本身的一部分。这允许解释器在运行时检查变量是什么,必要时转换它,并决定对其执行什么操作。
请注意,整数和单精度浮点数在内存中的表示方式非常不同(尽管在大多数语言和体系结构中都占用 4 个字节)。要知道如何显示或添加某些东西,编译器需要知道它是什么类型。编译器可以发出决定在运行时(一些托管程序)的代码,或者解释器可以决定在运行时如何处理它,或者编译器可能需要知道每个变量在编译时是什么,从而消除了需要在运行时在内存中存储和指定类型。
在 C 中,printf
是该语言表明它不是完全类型安全的领域之一(最初让我感到困惑的是,为什么有人会说 C 不是类型安全的,因为它显然有类型和似乎 强类型)。
请注意,在 C++ 中,使用 std::cout
,您可以输出 int
、double
、std::string
等,而无需告诉编译器您正在输出什么。这是因为对于 std::cout
,C++ 编译器将在编译时找出变量的类型并调用适当的 <<
重载,这会正确格式化表示变量的内存块,或者作为int
、一个 double
、一个 std::string
,或者随便什么。
然而,对于 printf
,该函数的规范旨在接受一堆 "data" 并打印它。我敢肯定,出现这种情况有一些有趣的历史原因。最明显的一个是可变长度参数不能用多种类型描述,因此传递给 printf 的参数是某种 "any" 类型..但是在编译函数体时,编译器知道传递的参数数量可变,但不知道每个参数的类型。
如果您为 printf
提供了数十个重载,其中包括不同可能类型的每种组合和排列,直到一定长度,那么您就可以避免向程序提示类型在运行时。
严格来说,我不认为这是对你问题的回答,而是听起来你误会了什么。
首先,您的假设有几个错误:
-
float
变量的声明不会告诉编译器 "allocate 4 bytes of storage in memory"。它告诉编译器分配一个float
,但它是在内存中分配,在寄存器中,完全隐式分配(没有 real 存储),或者可以想象,在某些完全其他方式取决于编译器。对于所有 C 规范问题,编译器可以使用您的声卡将float
存储在扬声器和麦克风之间的音频延迟循环中。 - 即使您将
float
传递给printf()
,编译器也会参与其中。您可能认为它只是将四个字节复制到堆栈上,但这只是 x86 ABI 的巧合。即使在 AMD64 上,这也是不正确的,可变参数函数的float
参数将在 SSE 寄存器中传递(与在整数 GPR 中传递的int
参数相反,因此编译器非常大有不同)。
当然,编译器确实没有告诉 printf()
去获取一个 float
来打印它,但是这个责任在你身上,但这不是因为编译器"ignores" 您的 float
变量的内容,而只是因为没有传递给函数的运行时类型信息,因此 printf()
需要有关从调用框架中获取什么样的值的信息.或者,换句话说,即使编译器知道值的数据类型并相应地调整生成的代码以匹配 ABI 和所有内容,它也没有义务告诉 printf()
它传递给它的是什么,因此您需要在原处告诉 printf()
它。
从这个角度来看,要回答您的问题,数据类型对编译器的意义永远不仅仅是它的存储大小,但我觉得您想问的实际问题是另外一回事。