64位系统上的NULL定义问题

时间:2009-11-04 14:38:31

标签: c++ linux gcc 64-bit null

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

我有一个实用功能:

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

其中concats 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. 你们中许多人建议传递(void *)0。这当然会起作用。功能在很多地方使用的问题,我的意思是很多地方。我想找到能给我C ++标准所承诺的解决方案 - 8字节大小的NULL。

5 个答案:

答案 0 :(得分:12)

在这种情况下没有“NULL定义问题”。您在代码中尝试使用NULL的方式存在问题。

NULL本身不能以可移植的方式传递给C / C ++中的可变参数函数。你必须在传递之前显式地转换它,即在你的情况下你必须传递(const char*) NULL作为参数列表的终结符。

您的问题被标记为C ++。无论如何,无论大小如何,在C ++中NULL总是被定义为整数常量。在C ++中将NULL定义为指针是非法的。由于您的函数需要一个指针(const char *),因此{C}代码中NULL的定义不会对它起作用。

对于更干净的代码,您可以定义自己的常量,例如

const char* const STR_TERM = NULL;

并在调用函数时使用它。但是你永远不能仅仅为NULL有意义地使用它。每当普通NULL作为可变参数传递时,它就是一个必须修复的明显的可移植性错误。

添加:您的更新声称“C ++标准承诺NULL的8字节大小”(在我假设的64位平台上)。这没有任何意义。关于NULL,C ++标准不承诺任何类似的内容。

NULL旨在用作右值。它没有特定的大小,并且没有有效使用NULL,其实际大小甚至可能非常重要。


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

  

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

     

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

答案 1 :(得分:5)

一个解决方案 - 可能是最好的,但肯定非常可靠 - 是将一个显式的null char指针传递给你的函数调用:

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 ++从业者普遍避免variable-length argument lists的原因;他们不是类型安全的。

答案 2 :(得分:3)

删除__GNUG__案例,并在第二个文件中反转ifdef / endif,BOTH文件执行:

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

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

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

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

答案 3 :(得分:1)

您可能无法修复包含,因为系统包含的是一个曲折的迷宫。

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

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

答案 4 :(得分:0)

我之前回复了以下答案。但后来我发现我误解了几条信息并给出了错误的答案。出于好奇,我用VS2008做了同样的测试,结果不一样。这只是大脑锻炼......

Why do you need the second one ? Both headers say the same thing.
And it does not even matter if you write 0 or NULL or ((void *)0)
All of them will take 8 bytes.

我在使用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字节......

Compiler treats 0 as it was a 32-bit data-type.
Although it does the correct displacement on the
stack pointer, the value pushed should not be 32-bit !

我用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表现不同。