【问题标题】:Perform 64 bit calculations in 64 bit executable在 64 位可执行文件中执行 64 位计算
【发布时间】:2016-09-16 07:26:40
【问题描述】:

我正在使用 MinGW64(带有 -m64 标志)和 Code::Blocks,我希望知道如何执行 64 位计算,而不必在乘以之前将一个非常大的数字转换为 int64_t。例如,这不会导致溢出:

int64_t test = int64_t(2123123123) * 17; //Returns 36093093091

没有强制转换,计算会像这样溢出:

int64_t test = 2123123123 * 17; //Returns 1733354723

VirusTotal 扫描确认我的可执行文件是 x64。

附加信息:操作系统为 Windows 7 x64。

【问题讨论】:

  • 如果您的 MinGW 安装包含 file command,您只需运行 file a.exe 即可打印有关文件的信息。例如在我的 Linux 桌面上:an-executable-I-just-compiled: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, ...。对于 Windows,您无需上传即可查看它是 32 位还是 64 位 PE 格式的可执行文件。

标签: c++ c++11 64-bit x86-64 integer-overflow


【解决方案1】:

为了兼容性,即使在 64 位编译中,默认的 int 类型仍然是 32 位。

我猜“最短”的版本是在数字上添加ll 后缀

int64_t test = 2123123123ll * 17;

另一种方法是将数字存储在它们自己的int64_t(或long long)类型的变量中,然后将变量相乘。通常在程序中很少有很多“幻数”硬编码到代码库中。

【讨论】:

  • 你的意思是什么性能?它需要更多内存(64 位而不是 32 位)。但基本上你告诉编译器你希望在编译期间这个值是long long,这样你就不需要在运行时进行转换。但是,如果您真的很关心性能,最好的检查方法是测试不同的版本并自己检查。
【解决方案2】:

一些背景:

曾几何时,大多数计算机都有 8 位算术逻辑单元和 16 位地址总线。我们称它们为 8 位计算机。

我们学到的第一件事是,现实世界的算术问题不能用 8 位表示。这就像试图用黑猩猩的算术能力来推理太空飞行。所以我们学会了编写多字加、乘、减和除序列。因为在大多数现实问题中,问题的数值域都大于 255。

我们短暂地拥有 16 位计算机(同样的问题也适用,65535 不足以对事物进行建模),然后很快将 32 位算术逻辑内置到芯片中。渐渐地,地址总线赶上了(20 位、24 位、32 位,如果设计师觉得奢侈的话)。

然后发生了一件有趣的事情。我们大多数人不再需要编写多字算术序列。事实证明,现实世界中的大多数(tm)整数问题都可以用 32 位(最多 40 亿)表示。

然后我们开始以比以往更快的速度生成更多数据,并且我们意识到需要处理更多内存。 64 位计算机最终成为常态。

但是,大多数现实世界的整数算术问题仍然可以用 32 位表示。对于大多数事情来说,40 亿是一个很大(足够)的数字。

因此,大概通过统计分析,您的编译器编写者决定在您的平台上,最有用的 int 大小是 32 位。对于 32 位算术(我们从第一天开始就需要),任何较小的都将是低效的,而任何较大的都会浪费空间/寄存器/内存/cpu 周期。

用 c++(和 c)表示整数文字会产生一个 int - 环境的自然算术大小。在今天,这几乎总是一个 32 位的值。

c++ 规范说两个整数相乘产生一个整数。如果没有,则将两个整数相乘将需要产生一个长整数。但是,乘以两个多头会产生什么?长长的?好的,这是可能的。现在,如果我们将它们相乘会怎样?长长长长?

就是这样。

int64_t x = 1 * 2; 将执行以下操作:

  1. 取值为 1 的整数(32 位)。
  2. 取值 2 的整数(32 位)。
  3. 将它们相乘,将结果存储为整数。如果算术溢出,那就这样吧。那是你的瞭望台。
  4. 将生成的整数(无论现在可能是什么)转换为 int64(可能在您的系统上为long int

所以简而言之,不。在问题中的代码 sn-p 中,拼写至少一个操作数的类型没有捷径可走。当然,您可以指定文字。但不能保证您系统上的long long(LL 文字后缀)与int64_t 相同。如果你想要一个int64_t,并且你希望代码是可移植的,你必须把它拼出来。

物有所值:

在后 c++11 世界中,所有对额外按键和非 DRYness 的担忧都会消失:

绝对是 int64:

auto test = int64_t(2123123123) * 17;

绝对是长长的:

auto test = 2'123'123'123LL * 17;

肯定是 int64,肯定是用 long long 初始化(可能会变窄,但没关系):

auto test = int64_t(36'093'093'091LL);

【讨论】:

  • 有趣的事实:在 x86-64 机器代码中,操作 64 位整数的指令比操作 32 位整数的相同指令长一个字节。 (例如 add rax, 1234add eax, 1234)。默认操作数大小是 32 位,就像 32 位 x86 一样。因此,决定 32 位仍然是一个很好的“自然”尺寸的不仅仅是 ABI 设计人员(编译人员),还有 硬件 架构师。 (为了源代码的可移植性,非常倾向于让int 与大多数其他平台的大小相同。)无论如何,这个决定是由 AMD 在 2000 年左右做出的,当时他们设计了 AMD64。
  • @FluorescentGreen5 同意,但在这种情况下我不推荐 LL 仍有两个原因。首先是这个问题没有标记为 c++11。第二个是可以想象,在未来的架构中,如果 long long 大于 64 位,将 LL 的结果分配给int64_t 实际上可能会导致缩小警告。拼出类型是 5 次击键(在 c++11 中,auto x = uint64_t(2) * 3; 的形式实际上是 DRY)
  • @FluorescentGreen5 你可能是对的。但“相当肯定”与“标准授权”不同。如果我正在阅读代码并且看到 long long x = 44444444LL; 这不会引起我的担忧。如果我看到int64_t x = 4444444LL;,我会想,“这家伙是否意识到这些不一定是同一类型?他是什么意思?long long 还是 int64?”。无论哪种情况,我都建议只指定一次类型 - 如果我真的想要一个 long long 和 auto x = std::int64_t(999) 如果我真的想要一个 64 位值,则使用 auto x = 999LL;。更清楚了。
  • @FluorescentGreen5 你输入了多少次?如果它小于 20,那么创建别名将需要更多的击键 :) 我想我们可以做 using i64 = int64_t; 但是阅读你的代码的人(到目前为止你最关心的问题)需要检查 i64 是否真的是他们所希望的是的。
  • 如果int64_t 的宽度不足以生成long long 常量,您将收到编译器警告(我认为)。多亏了优秀的编译器,我们不再需要担心将编译时常量转换为更窄的类型,只要该类型对于该特定值足够宽。将 LL 附加到一个常量并让编译器将其优化回它实际需要的任何宽度或您将其分配给的任何宽度几乎总是安全的。
【解决方案3】:

由于您很可能处于LP64 环境中,其中int 只有32 位,因此您必须小心表达式中的文字常量。最简单的方法是养成在文字常量上使用正确后缀的习惯,因此您可以将上面的内容写为:

int64_t test = 2123123123LL * 17LL;

【讨论】:

  • 不保证 LL 与 `int64_t' 的类型相同。
  • @RichardHodges: LL is at least 64 bits.
  • @RichardHodges:啊 - 没有注意到 C++ 标签 - 我以为这是一个 C 问题。无论如何,自 C99 和 C++11 以来最低 64 位。
【解决方案4】:

2123123123int(通常为 32 位)。

添加L 使其成为long2123123123L(通常为32 位或64 位,即使在64 位模式下也是如此)。

添加另一个L 使其成为long long2123123123LL(64 位或更多,从 C++11 开始)。

请注意,您只需将后缀添加到超出int 大小的常量。积分转换将负责产生正确的结果*。

(2123123123LL * 17)  // 17 is automatically converted to long long, the result is long long

* 但请注意:即使表达式中的单个常量适合 int整个操作 仍然会像 in

那样溢出
(1024 * 1024 * 1024 * 10)

在这种情况下,您应该确保以足够的宽度执行算术(考虑运算符优先级):

(1024LL * 1024 * 1024 * 10)

- 将以 64 位执行所有 3 个操作,结果为 64 位。

【讨论】:

  • 我的错误,我的意思是计算溢出,而不是个别数字
【解决方案5】:

编辑:文字常量(A.K.A. 幻数)不受欢迎,因此最好的方法是使用符号常量(const int64_t value = 5)。有关更多信息,请参阅What is a magic number, and why is it bad?最好不要阅读此答案的其余部分,除非您出于某种奇怪的原因真的想使用幻数。

另外,您可以使用#include <cstdint> 中的intptr_tuintprt_t 让编译器选择是使用int 还是__int64

对于那些偶然发现这个问题的人,数字末尾的“LL”可以解决问题,但不建议这样做,因为 Richard Hodges 告诉我,“long long”可能并不总是 64 位,并且可以增加未来的规模,尽管不太可能。有关更多信息,请参阅 Richard Hodge 的答案及其上的 cmets。

可靠的方法是将 `using QW = int_64t;` 放在顶部并使用 `QW(5)` 而不是 `5LL`。

我个人认为应该有一个选项来定义所有 64 位文字,而不必为其添加任何后缀或函数,并在必要时使用 `int32_t(5)`,因为某些程序不受此更改的影响。示例:仅使用数字进行正常计算,而不是依靠整数溢出来完成它的工作。问题是从 64 位到 32 位,而不是从 32 到 64 位,因为前 4 个字节被截断了。

【讨论】:

  • 我认为大多数编译器会在编译时常量表达式溢出其类型时发出警告。绝对是当单个文字对其类型而言太大时,但也可能是运算符的结果。
  • @PeterCordes 没错,但是如果溢出出现在很多地方怎么办?当然必须有一些编译器标志或代码行放在顶部以使所有文字都是 64 位的。
  • 这似乎是个好主意,但考虑一下其中的含义:现在您有了一段看起来像标准可移植 C++ 的代码,但它实际上只有在使用特殊编译器的特殊选项编译时才能正常工作.在其他编译器上,或者没有该选项,它将编译并运行,但给出错误的答案。默默地给出错误的答案是最糟糕的失败模式。我认为这就是为什么没有很多“以这种方式修改语言”编译器选项的原因,除了与现代标准之前的旧编译器兼容。
  • @PeterCordes 顶部的一行代码怎么样?
  • 我完全同意这将是一个不错的功能,我并不特别喜欢 C++ 的规则,或者很多事情(例如,没有可移植的方法来获得算术右移,AFAIK) .但在这一点上,我们基本上被他们困住了,除非我们切换到一种新的语言(比如 Rust)并放下遗留的包袱。由于无需更改语言(使用后缀)就可以轻松解决该问题,因此我怀疑未来对 C++ 的任何扩展都会改变这一点。
猜你喜欢
  • 1970-01-01
  • 2016-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-04
  • 2011-02-18
  • 2013-11-16
相关资源
最近更新 更多