Windows 中的 Null 是否等于零?

逆向工程 艾达 拆卸 恶意软件
2021-06-10 20:21:53

(这个问题指的是汇编语言。)我有点困惑。我多次遇到过应该返回句柄的 Windows 函数,如果不返回,则返回 NULL。为什么检查后检查零?零不等于 NULL。

例如:GetModuleHandleA:

https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlea

在此处输入图片说明

3个回答

在 C 和许多其他低级编程语言中,该术语NULL等效于0.

C 标准要求将 NULL 设为#define“实现定义的值”,但是所有实现都选择(出于显而易见的原因)0用于该目的。出于这个原因,如果您尝试“查看定义” NULL,许多 IDE 会让您排成一行#define NULL 0或类似的东西。

这有一个额外的好处,即NULL评估false使条件语句可读和直观。

从严格的标准遵循角度来看,正确的方法是使用NULL而不是 0,这就是大多数开发人员所做的。然而,编译器(或在 的情况下为预处理器#define NULL 0)会将其转换为0.

一些高级语言(例如 javascript 和 C++)使用特殊表达式来表示 null。一个例子是 C++ 的nullptr,因为 C++11 是NULL. Javascript 使用一个特殊的对象,null.

在查看 Windows API 调用或 C/C++ 代码的反汇编时,NULL 始终为 0,在 Visual Studio 中这是在 vcruntime.h 中定义的

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

但是,如果您正在查看更高级别的语言,NULL 不一定为零,例如在 .NET C# 代码中,如下所示:

if (args == null)
{
    Console.WriteLine("null!");
}

将编译为通用中间语言 (CIL)。你可以看到ldnull null 不仅仅是零。

IL_0001: ldarg.0
IL_0002: ldnull
IL_0003: ceq
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: brfalse.s IL_0016
IL_000a: ldstr "null!"
IL_000f: call void [mscorlib]System.Console::WriteLine(string)

ISO C 和 C++允许实现使用非零位模式作为空指针的对象表示,尽管需要将文字0(void*)0源中(在指针上下文中)作为空指针进行评估,相当于NULL. 基于像源定义推理#define NULL 0用C或C ++足够。

不过幸运的是每个人的理智,所有现代的C和C ++实现的x86(以及其他现代ISA)中0在ASM为NULL的位模式。 这使得不可移植的代码像memset(ptr_array, 0, size)预期的那样工作,相当于将每个元素设置为NULL.

NULL 宏何时不为 0?正在询问源级非零定义,但我认为这在现代 C 中是不允许的。答案提到了几台具有非零空指针位模式的历史机器。(即您在 asm 中看到的代码,例如do {...} while(p = p->next);


请记住,在 asm 中,指针只是 64 位(或 32 位)整数整个想法NULL是带内信令,而不是一些甚至不是指针大小的整数的特殊事物。所以我们必须选择一些常量。

0是一个方便的标记值,因为与检查任何其他值相比,许多 ISA 可以在非零值上更有效地进行分支。例如,ARM 必须cbnz在非零上进行分支,而无需cmp. x86 对test eax, eax/jnz而不是cmp eax, 0/ 进行了较小的代码大小优化jnz使用 CMP reg,0 与 OR reg,reg? 测试寄存器是否为零?)。如果 FLAGS 已经由另一条算术指令设置,则test不需要,但这对于空指针测试来说是不寻常的:通常你不会对指针进行数学运算,然后是 NULL。

(您没有在 asm 中看到该优化,因为您的调试版本在测试之前存储到内存中。)

此外,0易于生成。一些大的数字可能需要更大的指令,或者大多数指令,才能在寄存器中创建。(例如 x86xor eax,eax而不是mov eax, imm32)。零初始化静态存储static int *table = NULL;可以在 BSS 中而不是.data- 现代系统零初始化 BSS。


在某些系统(尤其是嵌入式)上,0地址并不特殊,实际上那里有系统管理的东西,比如中断处理程序表的开头。So0可以是有效地址,也可以等于NULL这有点糟糕,所以这就是人们可能真正想要一个非零对象表示的空指针的地方。@Simon里氏评论有关黑客攻击的ARM编译器使用0x20000000的空位模式。

在使用虚拟内存的系统(如 Windows)上,我们可以简单地避免映射包含该地址的页面,这有助于通过确保 NULL 取消引用实际出错来帮助检测错误。(请记住,在C和C ++的是,不确定的行为不是必需的故障,但它是如果它确实方便。)