2

不用说,使用硬编码的十六进制文字指针是一场灾难:

int *i = 0xDEADBEEF;
// god knows if that location is available

但是,使用十六进制文字作为变量到底有什么危险?

int i = 0xDEADBEEF;
// what can go wrong?

如果这些值由于在各种调试场景中的使用而确实是“危险的” ,那么这意味着即使我不使用这些文字,任何在运行期间碰巧偶然发现这些值之一的程序都可能崩溃。

有人愿意解释使用十六进制文字的真正危险吗?


编辑:只是为了澄清,我不是指源代码中常量的一般使用。我专门讨论使用十六进制值时可能出现的调试场景问题0xDEADBEEF,具体示例为.

4

10 回答 10

11

使用十六进制文字并不比任何其他类型的文字更危险。

如果您的调试会话最终将数据作为代码执行而您不打算这样做,那么无论如何您都处于痛苦的世界中。

当然,存在正常的“神奇值”与“命名常量”代码气味/清洁度问题,但这并不是我认为您正在谈论的那种危险。

于 2009-11-05T15:12:23.437 回答
5

除了少数例外,没有什么是“恒定的”。

我们更喜欢称它们为“慢变量”——它们的值变化如此缓慢,以至于我们不介意重新编译来改变它们。

但是,我们不希望在应用程序或测试脚本中都有很多 0x07 实例,其中每个实例都有不同的含义。

我们想在每个常数上加上一个标签,使它的含义完全明确。

if( x == 7 )

上述语句中的“7”是什么意思?是一样的吗

d = y / 7;

这和“7”的意思是一样的吗?

测试用例是一个稍微不同的问题。我们不需要对数字文字的每个实例进行广泛、仔细的管理。相反,我们需要文档。

我们可以——在一定程度上——通过在代码中包含一点提示来解释“7”的来源。

assertEquals( 7, someFunction(3,4), "Expected 7, see paragraph 7 of use case 7" );

一个“常数”应该被声明——并命名——恰好一次。

单元测试中的“结果”与常数不同,在解释它的来源时需要一点小心。

于 2009-11-05T15:13:48.013 回答
3

十六进制文字与像 1 这样的十进制文字没有什么不同。值的任何特殊意义都取决于特定程序的上下文。

于 2009-11-05T15:12:14.497 回答
2

没有理由不应该分配0xdeadbeef给变量。

但是,试图分配 decimal3735928559或 octal33653337357或最糟糕的: binary的程序员有祸了11011110101011011011111011101111

于 2009-11-05T15:19:23.587 回答
2

我相信今天早些时候在 IP 地址格式问题中提出的问题与一般十六进制文字的使用无关,而是与 0xDEADBEEF 的具体使用有关。至少,我是这么读的。

特别是使用 0xDEADBEEF 存在问题,尽管在我看来它是一个小问题。问题是许多调试器和运行时系统已经选择这个特定值作为标记值来指示未分配的堆、堆栈上的错误指针等。

我不记得哪些调试和运行时系统使用了这个特定的值,但多年来我已经多次看到它以这种方式使用过。如果您在其中一种环境中进行调试,则代码中 0xDEADBEEF 常量的存在将与未分配 RAM 或其他任何内容中的值无法区分,因此最好的情况是您不会有有用的 RAM 转储,最坏的情况是您会收到警告从调试器。

无论如何,这就是我认为原始评论者告诉您“在各种调试场景中使用”不好时的意思。

于 2009-11-05T15:27:53.623 回答
2

大端还是小端?

一个危险是当常量被分配给具有不同大小成员的数组或结构时;编译器或机器(包括 JVM 与 CLR)的字节序会影响字节的顺序

当然,这个问题也适用于非常数值。

这是一个公认的人为的例子。最后一行之后的buffer[0]的值是多少?

const int TEST[] = { 0x01BADA55,  0xDEADBEEF };
char buffer[BUFSZ];

memcpy( buffer, (void*)TEST, sizeof(TEST));
于 2009-11-05T15:50:03.680 回答
1

我认为将其用作值没有任何问题。毕竟只是一个数字。

于 2009-11-05T15:13:31.967 回答
1

在正确的上下文中使用硬编码的十六进制值作为指针(如您的第一个示例)没有危险。特别是在进行非常底层的硬件开发时,这是访问内存映射寄存器的方式。(例如,尽管最好用#define 给它们命名。)但是在应用程序级别,您永远不需要做这样的分配。

于 2009-11-05T15:22:55.410 回答
1

我使用 CAFEBABE 我之前没有见过任何调试器使用它。

于 2010-02-03T02:00:40.170 回答
0
int *i = 0xDEADBEEF;
// god knows if that location is available

int i = 0xDEADBEEF;
// what can go wrong?

在这两种情况下,我看到的危险是相同的:您创建了一个没有直接上下文的标志值。在任何一种情况下,都不i会让我知道 100、1000 或 10000 行存在与之相关的潜在关键标志值。你种的是一个地雷错误,如果我不记得在所有可能的用途中检查它,我可能会面临一个可怕的调试问题。will 现在的每次使用i都必须如下所示:

if (i != 0xDEADBEEF) { // Curse the original designer to oblivion
    // Actual useful work goes here
}

i对需要在代码中使用的所有 7000 个实例重复上述操作。

现在,为什么上述情况比这更糟糕?

if (isIProperlyInitialized()) { // Which could just be a boolean
    // Actual useful work goes here
}

至少,我可以发现几个关键问题:

  1. 拼写:我是一个糟糕的打字员。您在代码审查中发现 0xDAEDBEEF 的难易程度如何?还是 0xDEADBEFF?另一方面,我知道我的编译会立即在 isIProperlyInitialised() 上失败(在此处插入强制性的 sz辩论)。
  2. 露意。您没有尝试在代码中隐藏您的标志,而是有意创建了一个其他代码可以看到的方法。
  3. 耦合的机会。指针或引用完全有可能连接到松散定义的缓存。初始化检查可能会被重载以首先检查值是否在缓存中,然后尝试将其带回缓存中,如果一切都失败,则返回 false。

简而言之,编写你真正需要的代码就像创造一个神秘的魔法值一样容易。未来的代码维护者(很可能就是你)会感谢你的。

于 2009-11-05T17:27:36.813 回答