龙空技术网

整数问题示例

码农世界 48

前言:

现时姐妹们对“c语言单字节整数”都比较关注,小伙伴们都想要分析一些“c语言单字节整数”的相关知识。那么小编也在网摘上网罗了一些对于“c语言单字节整数””的相关文章,希望你们能喜欢,我们快快来学习一下吧!

示例 1:小型数据库主键

最经典(也是最痛苦!)的整数问题之一是:

您创建了一个数据库表,其中主键是 32 位无符号整数,认为“4 亿行对任何人来说都应该足够了!您取得了巨大的成功,最终,您的表将接近 4 亿行哦不!您需要执行数据库迁移以将主键切换为 64 位整数

如果主键实际上达到其最大值,我不确定确切是什么 发生,我想您将无法创建任何新的数据库行,并且 对于您非常成功的服务来说,这将是非常糟糕的一天。

示例 2:整数溢出/下溢

这是一个Go程序:

package mainimport "fmt"func main() {	var x uint32 = 5	var length uint32 = 0	if x < length-1 {		fmt.Printf("%d is less than %d\n", x, length-1)	}}

这有点神秘地打印出来:

5 is less than 4294967295

这是真的,但这不是你所期望的。

这是怎么回事?

0 - 1等于 4 个字节。0xFFFFFFFF

有两种方法可以解释这 2 个字节:

作为有符号整数 (-1)作为无符号整数 (4294967295)

Go 这里被视为无符号整数,因为我们定义了 和 uint32s(“u”代表“无符号”)。所以它正在测试5是否小于4294967295,确实如此!length - 1xlength

我们该怎么做呢?

我实际上不确定是否有任何方法可以在 Go 中自动检测整数溢出错误。(尽管看起来 2019 年有一个 GitHub 问题,并进行了一些讨论)

关于其他语言的一些简要说明:

许多语言(Python,Java,Ruby)根本没有无符号整数,所以这个特定的问题不会出现。在 C 语言中,可以使用 .然后,如果您的代码有这样的溢出/下溢,程序将崩溃。clang -fsanitize=unsigned-integer-overflow同样,在 Rust 中,如果您在调试模式下编译程序,如果存在整数溢出,它将崩溃。但在发布模式下,它不会崩溃,它只会愉快地决定 0 - 1 = 4294967295。

Rust 不检查溢出的原因,如果你编译你的程序 发布模式(以及 C 和 Go 不检查的原因)是 – 这些检查是 贵!整数算术是许多计算的重要组成部分,并且 确保每个添加都不会溢出会使速度变慢。

旁白:计算机如何表示负整数?

我在上一节中提到,这可能意味着或.你可能会想——什么???为什么会意味着 ?0xFFFFFFFF-142949672950xFFFFFFFF-1

因此,让我们谈谈计算机如何表示负整数。

我将简化并讨论 8 位整数而不是 32 位整数, 因为它们的数量较少,而且它的工作方式基本相同。

您可以使用 256 位整数表示 8 个不同的数字:0 到 255

00000000 -> 000000001 -> 100000010 -> 2...11111111 -> 255

但是,如果要表示整数怎么办?我们仍然只有 8 个 位!因此,我们需要重新分配其中一些并将它们视为负数 相反。

以下是大多数现代计算机的做法:

每个 128 或更大的数字都变为负数如何知道它是哪个负数:取你期望的正整数,然后减去 256

所以 255 变成 -1,128 变成 -128,200 变成 -56。

以下是一些位到数字的映射:

00000000 -> 000000001 -> 100000010 -> 201111111 -> 12710000000 -> -128 (previously 128)10000001 -> -127 (previously 129)10000010 -> -126 (previously 130)...11111111 -> -1 (previously 255)

这给了我们 256 个数字,从 -128 到 127。

和 (或 或 255) 为 -1。111111110xFF

对于 32 位整数,情况相同,除了“每个大于 2^31 的数字都变为负数”和“减去 2^32”。对于其他整数大小也是如此。

这就是我们最终得到意义 -1 的方式。0xFFFFFFFF

有多种表示负整数的方法

我们刚才谈到的表示负整数的方式(“它是等效的正整数,但你减去 2^n”)被称为 <> 补码,在现代计算机上是最常见的。还有其他几种方法 不过,维基百科文章有一个列表。

奇怪的事情:-128 的绝对值为负数

这个Go程序有一个非常简单的函数,可以计算整数的绝对值:abs()

package mainimport (	"fmt")func abs(x int8) int8 {	if x < 0 {		return -x	}	return x}func main() {	fmt.Println(abs(-127))	fmt.Println(abs(-128))}

这将打印出来:

127-128

这是因为有符号的 8 位整数从 -128 到 127 – 没有 +128! 当您尝试执行此操作时,某些程序可能会崩溃(这是溢出),但是Go 不。

现在我们已经讨论了一堆有符号整数,让我们深入研究它们如何导致问题的另一个示例。

示例 3:在 Java 中解码二进制格式

假设您正在解析 Java 中的二进制格式,并且想要获得第一个 字节的 4 位。正确答案是9。0x90

public class Main {    public static void main(String[] args) {        byte b = (byte) 0x90;        System.out.println(b >> 4);    }}

这将打印出“-7”。不对!

这是怎么回事?

关于Java,我们需要知道两件事才能理解这一点:

Java没有无符号整数。Java不能右移字节,它只能移位整数。因此,无论何时移动一个字节,都必须将其提升为整数。

让我们分解这两个事实对我们的小计算意味着什么:b >> 4

以位为单位,是 。这以 1 开头,这意味着它大于 128,这意味着它是一个负数0x9010010000Java 看到 并决定提升为整数,以便它可以移动它>>0x90将负字节转换为 32 位整数的方法是在开头添加一堆 s。所以现在我们的 32 位整数是 ( 是 15,或10xFFFFFF90F1111)现在我们右移()。默认情况下,Java 执行带符号移位,这意味着如果它是正数,它会在开头加 0,如果它是负数,则在开头加 1。(是 Java 中的无符号转换)b >> 4>>>我们最终得到(切断了最后 4 位并在开始时添加了更多 1)0xFFFFFFF9作为有符号整数,即 -7!你能做些什么呢?

我不是在 Java 中执行此操作的实际惯用方式,但我天真地这样做的方式 解决此问题的方法是在进行正确的班次之前放入位掩码。所以 而不是:

b >> 4

我们会写

(b & 0xFF) >> 4

b & 0xFF看起来是多余的(已经是一个字节!),但实际上并不是因为被提升为整数。bb

现在不是 ,我们最终计算 ,这是我们想要的结果:9。0x90 -> 0xFFFFFF90 -> 0xFFFFFFF90x90 -> 0xFFFFFF90 -> 0x00000090 -> 0x00000009

当我们实际尝试时,它会打印出“9”。

此外,如果我们使用带有无符号整数的语言,那么自然的方式是 处理这个问题是将值视为第一个中的无符号整数 地方。但这在Java中是不可能的。

示例 4:将 IP 地址或字符串误解为整数

我不知道这在技术上是否是“整数问题”,但这很有趣 所以我会提到它:海湾边的瑞秋有一堆很棒的 非整数的事物被解释为整数的示例。为 例如,“HTTP”是和现在是。0x485454502130706433127.0.0.1

她指出,您实际上可以ping任何整数,它会将该整数转换为IP地址,例如:

$ ping 2130706433PING 2130706433 (127.0.0.1): 56 data bytes$ ping 132848123841239999988888888888234234234234234234PING 132848123841239999988888888888234234234234234234 (251.164.101.122): 56 data bytes

(我实际上不确定 ping 如何解析第二个整数,或者为什么 ping 接受这些大于 2^64 个整数的巨大整数作为有效输入,但这是一件有趣的奇怪事情)

示例 5:整数溢出导致的安全问题

另一个整数溢出示例:此处搜索涉及整数溢出的 CVE。 有很多!我不是安全人员,但这里有一个随机示例:这个 json 解析库错误

我对 json 解析错误的理解大致是:

您加载的 JSON 文件为 3GB 或其他内容,或 3,000,000,000由于整数溢出,代码分配接近 0 字节的内存,而不是 ~3GB 的内存量但是 JSON 文件仍然是 3GB,因此它被复制到具有几乎 0 字节内存的微小缓冲区中这将覆盖不应该覆盖的各种其他内存

CVE说“此漏洞主要影响进程可用性”,我 思考的意思是“程序崩溃”,但有时这种事情很多 更糟,并可能导致任意代码执行。

我的印象是,有各种各样的不同口味 整数溢出导致的安全漏洞。

示例 6:神秘字节顺序的情况

有人说他们正在做科学计算,有时他们需要 读取包含字节顺序未知的数据的文件。

让我们发明一个小例子:假设您正在读取一个包含 4 个的文件 字节 - 、、 和(按此顺序),您碰巧知道 表示一个 4 字节整数。有两种方法可以解释该整数:00001281

0x00001281(翻译为 4737)。此顺序称为“大端序”0x81120000(翻译为2165440512)。这个顺序被称为“小端序”。

是哪一个?好吧,也许该文件包含一些指定 字节序。或者,也许您碰巧知道它是在什么机器上生成的,并且 计算机使用的字节顺序。或者也许你只是阅读了一堆值, 尝试这两种订单,并找出哪个更有意义。也许2165440512也是 大到在数据应该意味着什么的上下文中有意义,或者 也许太小了。4737

关于这一点的更多说明:

这不仅仅是整数的问题,浮点数有字节 也订购从网络读取数据时也会出现这种情况,但在这种情况下 字节顺序不是一个“谜”,它只是大端序。但是x86 机器(和许多其他机器)是小端序,所以你必须交换字节 您所有数字的顺序。示例 7:负数的模

这更像是一个关于不同编程语言如何设计数学库的设计决策,但它仍然有点奇怪,很多人都提到了它。

假设您在程序中编写,或者.结果如何?-13 % 313 % -3

事实证明,不同的编程语言的做法不同,因为 例子在Python中,但在Javascript中。-13 % 3 = 2-13 % 3 = -1

这篇博文中有一张表格 描述了一堆不同编程语言的选择。

示例 8:编译器删除整数溢出检查

我们已经听到了很多关于整数溢出以及为什么它是不好的。所以让我们 想象一下,你试图确保安全,并在你的程序中包括一些检查 - 之后 每次添加,您都要确保计算不会溢出。喜欢这个:

#include <stdio.h>#define INT_MAX 2147483647int check_overflow(int n) {    n = n + 100;    if (n + 100 < 0)        return -1;    return 0;}int main() {    int result = check_overflow(INT_MAX);    printf("%d\n", result);}

check_overflow这里应该返回(失败),因为大于最大整数大小。-1INT_MAX + 100

$ gcc  check_overflow.c  -o check_overflow && ./check_overflow-1 $ gcc -O3 check_overflow.c  -o check_overflow && ./check_overflow0

这很奇怪——当我们使用 编译时,我们得到了我们期望的答案,但是 有了,我们得到了不同的答案。为什么?gccgcc -O3

这是怎么回事?

我的理解(可能是错误的)是:

C 中的有符号整数溢出是未定义的行为。我认为那是 因为不同的 C 实现可能使用不同的表示形式 有符号整数(也许他们使用的是 one 的补码而不是 2 的补码) 补体什么的)C语言中的“未定义行为”意味着“编译器在那之后可以自由地做任何它想做的事情”(参见这篇文章 对于未定义的行为,Raph Levine的任何事情都是可能的)一些编译器优化假设未定义的行为永远不会 发生。他们可以自由地这样做,因为 - 如果这种未定义的行为确实发生了,那么他们就可以做任何他们想做的事情,所以“运行 我优化的代码假设这永远不会发生“很好。所以这个检查是无关紧要的——如果确实如此的话 发生,这将是未定义的行为,因此无需执行 该 if 语句的内容。if (n + 100 < 0)

所以,这很奇怪。我不会在这里写一个“你能做些什么?”的部分,因为我已经超出了我的深度。

不过,我当然不会预料到这一点。

我的印象是,“未定义的行为”实际上是一个C/C++概念,并且 以相同的方式存在于其他语言中,除了“您的 程序以不正确的方式调用了一些 C 代码,并且该 C 代码做了一些事情 因为未定义的行为而奇怪”。当然,这种情况一直在发生。

示例 9:&&&拼写错误

这个被提到是一个非常令人沮丧的错误。假设您有两个整数 并且您要检查它们是否都是非零的。

在Javascript中,你可以写:

if a && b {    /* some code */}

但你也可以打错字并键入:

if a & b {    /* some code */}

这仍然是完全有效的代码,但它意味着完全不同的东西 – 它是按位的,而不是布尔值和。让我们进入一个Javascript 控制台并查看按位与布尔值以及 for 和 :94

> 9 && 44> 9 & 40> 4 && 55> 4 & 54

很容易想象这会变成一个非常烦人的错误,因为它会 间歇性 – 如果真实,通常确实是真实的。x & yx && y

怎么办呢?

对于Javascript,ESLint有一个无位检查检查),它 要求您手动标记“不,我实际上知道我在做什么,我想做什么 按位和“,如果您在代码中使用按位和。我相信许多其他棉绒 进行类似的检查。

标签: #c语言单字节整数 #java无符号整数 #java从文件读取整数数据