Language agnostic 使用magic调试值(例如0xDEADBEEF)作为文本的危险到底是什么?

Language agnostic 使用magic调试值(例如0xDEADBEEF)作为文本的危险到底是什么?,language-agnostic,programming-languages,hex,literals,Language Agnostic,Programming Languages,Hex,Literals,不用说,使用硬编码的十六进制文字指针是一场灾难: int *i = 0xDEADBEEF; // god knows if that location is available 然而,使用十六进制文字作为变量值到底有什么危险 如果这些值由于它们的属性而确实是“危险的”,那么这意味着即使我不使用这些文本,在运行时偶然发现其中一个值的任何程序都可能崩溃 有人愿意解释使用十六进制文字的真正危险吗 编辑:我只是想澄清一下,我指的不是源代码中常量的一般用法。我特别讨论的是使用十六进制值时可能出现的调试

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

int *i = 0xDEADBEEF;
// god knows if that location is available
然而,使用十六进制文字作为变量值到底有什么危险

如果这些值由于它们的属性而确实是“危险的”,那么这意味着即使我不使用这些文本,在运行时偶然发现其中一个值的任何程序都可能崩溃

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



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

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

使用十六进制文字比任何其他类型的文字都没有危险

如果您的调试会话最终以代码形式执行数据,而您并不打算这样做,那么您将陷入痛苦的世界


当然,存在正常的“神奇值”与“命名恒定值”代码气味/清洁度问题,但这并不是我想你说的那种真正的危险。

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

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

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

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

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

if( x == 7 )
在上面的陈述中,“7”是什么意思?这和我的想法是一样的吗

d = y / 7;
“7”的意思是一样的吗

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

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

assertEquals( 7, someFunction(3,4), "Expected 7, see paragraph 7 of use case 7" );
一个“常量”应该只声明一次——并命名一次


单元测试中的“结果”与常量不是一回事,在解释其来源时需要小心谨慎。

没有理由不将
0xdeadbeef
赋值给变量


但是,对于试图分配十进制
3735928559
或八进制
33653337357
,或者最糟糕的是:二进制
11011110101011011111101111
的程序员来说,在正确的上下文中为指针使用硬编码的十六进制值(如第一个示例)是没有危险的。特别是,在进行非常低级的硬件开发时,这是访问内存映射寄存器的方式。(尽管最好给他们一个带有#define的名字,例如。)但在应用程序级别,你不应该做这样的赋值。

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

虽然我认为这是一个小问题,但人们特别关注使用牛肉。问题是,许多调试器和运行时系统已经将此特定值添加为标记值,以指示未分配的堆、堆栈上的错误指针等

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


不管怎样,我认为这就是最初的评论者告诉你它不适合“在各种调试场景中使用”的意思。

Big-Endian还是Little-Endian?

一种危险是,将常量分配给具有不同大小成员的数组或结构时;编译器或计算机(包括JVM与CLR)的尾端将影响字节的顺序

当然,非常量值也是如此

这是一个公认的人为的例子。缓冲区[0]在最后一行之后的值是多少

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

memcpy( buffer, (void*)TEST, sizeof(TEST));
我看到的危险在这两种情况下都是一样的:您创建了一个没有直接上下文的标志值。在这两种情况下,
i
都不会让我知道100行、1000行或10000行存在与之相关的潜在临界标志值。你植入的是一个地雷漏洞,如果我不记得在每一个可能的使用中检查它,我可能会面临一个可怕的调试问题。
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
}
至少,我可以发现几个关键问题:

  • 拼写:我打字打得很糟糕。您在代码审查中发现0xDAEDBEEF有多容易?还是说你死了?另一方面,我知道我的编译会在IsiProperlyInitialized()上立即呕吐(在这里插入强制性的s与z辩论)
  • 意义的揭示。您不是试图在代码中隐藏您的标志,而是有意创建了一个代码其余部分可以看到的方法
  • 耦合机会。指针或引用完全可能连接到松散定义的缓存。初始化检查可以重载到c
    if (i != 0xDEADBEEF) { // Curse the original designer to oblivion
        // Actual useful work goes here
    }
    
    if (isIProperlyInitialized()) { // Which could just be a boolean
        // Actual useful work goes here
    }