64位系统上的NULL定义问题

我使用gcc 4.1.2在RHEL 5.1 64位平台上运行。

我有一个实用的function:

void str_concat(char *buff, int buffSize, ...); 

其中concat char *在可变参数列表(…)中传递,而最后一个参数应为NULL,以指定参数结束。 在64位系统上,NULL是8个字节。

现在的问题。 我的应用程序包括直接/间接2 stddef.h文件。

首先是/usr/include/linux/stddef.h ,它定义了NULL,如下所示:

 #undef NULL #if defined(__cplusplus) #define NULL 0 #else #define NULL ((void *)0) #endif 

第二个是/usr/lib/gcc/x86_64-redhat-linux/4.1.2/include/stddef.h

 #if defined (_STDDEF_H) || defined (__need_NULL) #undef NULL /* in case <stdio.h> has defined it. */ #ifdef __GNUG__ #define NULL __null #else /* G++ */ #ifndef __cplusplus #define NULL ((void *)0) #else /* C++ */ #define NULL 0 #endif /* C++ */ #endif /* G++ */ #endif /* NULL not defined and <stddef.h> or need NULL. */ #undef __need_NULL 

当然,我需要第二个,因为它将NULL定义为__null(8字节),而第一个定义为整数0(4字节)。

我如何防止/usr/include/linux/stddef.h被不正确地包含在内?

UPD:

  1. 编译线非常简单:

    g ++ -Wall -fmessage-length = 0 -g -pthread

  2. 你们中许多人build议通过(void *)0。 这当然会起作用。 function在很多地方使用的问题,我的意思是很多地方。 我想find解决scheme,将给我什么C + +标准的承诺 – 8字节大小的NULL。

Solutions Collecting From Web of "64位系统上的NULL定义问题"

在这种情况下,没有“NULL定义问题”。 在您的代码中如何尝试使用NULL存在一个问题。

在C / C ++中, NULL本身不能移植到可变参数函数中。 你必须在传递之前明确地转换它,也就是说,你必须传递(const char*) NULL作为参数列表的终止符。

你的问题被标记为C ++。 在任何情况下,不管大小如何,在C ++中, NULL总是被定义为一个整数常量。 在C ++中将NULL定义为指针是非法的。 由于你的函数需要一个指针( const char * ),因此在C ++代码中, NULL定义将不再适用。

为了更清洁的代码,你可以定义你自己的常量,比如

 const char* const STR_TERM = NULL; 

并用它来调用你的函数。 但是你将永远无法仅仅为了这个目的而使用NULL 。 每当一个普通的NULL被作为可变参数传递时,它就是一个公然的可移植性bug,必须修正。

补充说:你的更新声明“C ++标准承诺NULL的8字节大小”(我假设在一个64位平台上)。 这只是没有任何意义。 C ++标准不承诺任何关于NULL东西。

NULL旨在用作右值。 它没有具体的大小,并且没有有效的使用NULL ,其实际大小甚至可能是远程的。


引用ISO / IEC 14882:1998第18.1节“类型”第4段:

宏NULL是本国际标准(4.10)中定义的一个实现定义的C ++空指针常量。 180)

180)可能的定义包括0和0L,但不包括(void *)0。

一个解决方案 – 可能甚至是最好的,但肯定是非常可靠的 – 是将一个显式的空字符指针传递给你的函数调用:

 str_concat(buffer, sizeof(buffer), "str1", "str2", ..., (char *)0); 

要么:

 str_concat(buffer, sizeof(buffer), "str1", "str2", ..., (char *)NULL); 

例如,对于POSIX系统中的execl()函数,这是一个标准推荐的做法,而且也是出于完全相同的原因 – 可变长度参数列表的尾随参数受到通常的促销(char或short to int; float to double ),但不能以其他方式安全。

这也是为什么C ++从业者通常避免可变长度的参数列表 ; 他们不是类型安全的。

删除__GNUG__情况,并倒置第二个文件中的ifdef / endif,两个文件做:

 #undef NULL #if defined(__cplusplus) #define NULL 0 #else #define NULL ((void *)0) #endif 

也就是说,它们将NULL定义为((void *)0)用于C编译,0定义为C ++。

所以简单的答案是“不要编译为C ++”。

你真正的问题是你希望在你的可变参数列表中使用NULL,并结合你的编译器的不可预知的参数大小。 你可能会尝试写“(void *)0”而不是NULL来终止你的列表,强制编译器传递一个8字节的指针而不是4字节的int。

你可能无法修复包括因为系统包括一个曲折的迷宫。

您可以通过使用(void *)0或(char *)0而不是NULL来解决问题。

在考虑之后,我拒绝了我之前重新定义NULL的想法。 这将是一件坏事,可能会弄乱很多其他的代码。

我以前答复下面的答案。 但后来我看到我误解了几个信息,并给出了不正确的答案。 出于好奇,我用VS2008做了同样的测试,得到了不同的结果。 这只是脑力锻炼…

你为什么需要第二个? 两个头文件都是一样的。
而且,如果你写0或NULL或((void *)0)
所有这些将需要8个字节。

我用GCC 4.1.3做了一个64位平台的快速测试

 #include <string.h> void str_concat(char *po_buf, int pi_max, ...) { strcpy(po_buf, "Malkocoglu"); /* bogus */ } int main() { char buf[100]; str_concat(buf, 100, "abc", 1234LL, "def", 5678LL, "ghi", 2345LL, "jkl", 6789LL, "mno", 3456LL, 0, "pqx", 0); return 1; } 

这是由编译器生成的程序集…

 main: .LFB3: pushq %rbp .LCFI3: movq %rsp, %rbp .LCFI4: subq $192, %rsp .LCFI5: leaq -112(%rbp), %rdi movl $0, 64(%rsp) 0 movq $.LC2, 56(%rsp) "pqx" movl $0, 48(%rsp) 0 movq $3456, 40(%rsp) 3456LL movq $.LC3, 32(%rsp) "mno" movq $6789, 24(%rsp) 6789LL movq $.LC4, 16(%rsp) "jkl" movq $2345, 8(%rsp) 2345LL movq $.LC5, (%rsp) "ghi" movl $5678, %r9d 5678LL movl $.LC0, %r8d "def" movl $1234, %ecx 1234LL movl $.LC1, %edx "abc" movl $100, %esi 100 movl $0, %eax call str_concat movl $1, %eax leave ret 

注意所有的堆栈位移是8字节…

编译器将其视为32位数据类型。
虽然它做了正确的位移
堆栈指针,推送的值不应该是32位!

我用VS2008做了同样的测试,装配输出如下:

 mov QWORD PTR [rsp+112], 0 lea rax, OFFSET FLAT:$SG3597 mov QWORD PTR [rsp+104], rax mov QWORD PTR [rsp+96], 0 mov QWORD PTR [rsp+88], 3456 ; 00000d80H lea rax, OFFSET FLAT:$SG3598 mov QWORD PTR [rsp+80], rax mov QWORD PTR [rsp+72], 6789 ; 00001a85H lea rax, OFFSET FLAT:$SG3599 mov QWORD PTR [rsp+64], rax mov QWORD PTR [rsp+56], 2345 ; 00000929H lea rax, OFFSET FLAT:$SG3600 mov QWORD PTR [rsp+48], rax mov QWORD PTR [rsp+40], 5678 ; 0000162eH lea rax, OFFSET FLAT:$SG3601 mov QWORD PTR [rsp+32], rax mov r9d, 1234 ; 000004d2H lea r8, OFFSET FLAT:$SG3602 mov edx, 100 ; 00000064H lea rcx, QWORD PTR buf$[rsp] call ?str_concat@@YAXPEADHZZ ; str_concat 

这次编译器生成不同的代码,它将0视为一个64位数据类型(注意QWORD关键字)。 价值和堆栈位移是正确的。 VS和GCC的行为不同。