64位系统上的NULL定义问题

发布于 2024-08-10 20:44:43 字数 1259 浏览 3 评论 0原文

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

我有一个实用函数:

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

它连接在可变参数列表(...)中传递的 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。

I'm running on RHEL 5.1 64 bit platfrom using gcc 4.1.2.

I have a utility function:

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

which concats char * passed in variadic list(...), while last argument should be NULL, to designate end of the arguments. On 64 bit system NULL is 8 bytes.

Now to the problem. My application includes directly/indirectly 2 stddef.h files.

First one is /usr/include/linux/stddef.h which defines NULL as following:

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

The second one is /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

Of course I need the 2nd one, since it defines NULL as __null (8 bytes), while 1st one defines it as integer 0 (4 bytes).

How do I prevent /usr/include/linux/stddef.h to be inderectly included?

UPD:

  1. Compilation line is pretty straightforward:

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

  2. Many of you advised to pass (void *)0. This of course will work. The problem that the function is used in many, I mean many places. I'd like to find solution that will give me what C++ standard promises - NULL of 8 byte size.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

无悔心 2024-08-17 20:44:43

在这种情况下不存在“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++ 标准承诺 8 字节大小的 NULL”(我猜是在 64 位平台上)。这没有任何意义。 C++ 标准不承诺任何关于 NULL 的事情。

NULL 旨在用作右值。它没有具体的大小,并且在其实际大小可能有一点影响的情况下也无法有效使用 NULL。


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

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

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

There's no "NULL definiton problem" in this case. There's a problem with how you are trying to use NULL in your code.

NULL cannot be portably passed to variadic functions in C/C++ by itself. You have to explicitly cast it before passing, i.e. in your case you have to pass (const char*) NULL as the terminator of the argument list.

Your question is tagged as C++. In any case, regardless of size, in C++ NULL will always be defined as an integer constant. It is illegal in C++ to define NULL as a pointer. Since your function expects a pointer (const char *), no definition of NULL will ever work for it in C++ code.

For cleaner code you can define your own constant, like

const char* const STR_TERM = NULL;

and use it in the calls to your function. But you will never be able to meaningfully use just NULL for that purpose. Whenever a plain NULL is passed as a variadic argument, it is a blatant portability bug that has to be fixed.

Added: your update claims that "C++ standard promises NULL of 8 byte size" (on a 64-bit platform I presume). This just doesn't make any sense. C++ standard does not promise anything like that about NULL.

NULL is intended to be used as an rvalue. It has no specific size and there's no valid use of NULL where its actual size might even remotely matter.


Quoting from ISO/IEC 14882:1998, section 18.1 'Types', paragraph 4:

The macro NULL is an implementation defined
C++ null pointer constant in this International Standard
(4.10).180)

180) Possible definitions include 0 and 0L, but not (void*)0.

旧人哭 2024-08-17 20:44:43

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

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

或者:

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

这是 POSIX 系统中 execl() 函数的标准推荐做法,例如,出于完全相同的原因 - 可变长度参数列表的尾随参数会受到通常的提升(char 或 Short 到 int;float 到 double),但不能是类型安全的。

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

One solution - possibly even the best, but certainly very reliable - is to pass an explicit null char pointer to your function calls:

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

or:

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

This is standard recommended practice for the execl() function in POSIX systems, for example, and for precisely the same reason - the trailing arguments of a variable-length argument list are subject to usual promotions (char or short to int; float to double), but cannot otherwise be type safe.

It is also why C++ practitioners generally avoid variable-length argument lists; they are not type safe.

等风也等你 2024-08-17 20:44:43

删除 __GNUG__ 大小写,并反转第二个文件中的 ifdef/endif,这两个文件都会执行以下操作:

#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。

Removing the __GNUG__case, and inverting the ifdef/endif in the second file, BOTH files do:

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

Which is to say that they define NULL as ((void *)0) for C compilations and 0 for C++.

So the simple answer is "Don't compile as C++".

Your real problem is your desire to use NULL in your variadic arugment list, combined with your compiler's unpredictable argument sizing. What you MIGHT try is writing "(void *)0" instead of NULL to terminate your list, and force the compiler pass an 8-byte pointer instead of a 4-byte int.

ま昔日黯然 2024-08-17 20:44:43

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

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

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

You may not be able to fix the includes because system includes are a twisty maze.

You might fix the problem by using (void*)0 or (char*)0 instead of NULL.

After considering it I am rejecting my previous idea of redefining NULL. That would be a bad thing to do and could mess up a lot of other code.

橙幽之幻 2024-08-17 20:44:43

我之前回复过以下答案。但后来我发现我误解了一些信息并给出了错误的答案。出于好奇,我用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.

我在 64 位平台上使用 GCC 4.1.3 进行了快速测试,

#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 的行为不同。

I previously replied with the below answer. But then I saw that I misinterpreted several information and gave an incorrect answer. Just out of curiosity, I did the same test with VS2008 and I got different results. This is just brain exercise...

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.

I did a quick test on a 64-bit platform with GCC 4.1.3

#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;
}

And this is the assembly generated by the compiler...

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

Notice all the stack displacements are 8 byte...

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 !

I did the same test with VS2008 , the assembly output is as follows :

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

This time compiler generates different code and it treats 0 as an 64-bit data-type (notice the QWORD keyword). Both the value and stack displacement is correct. VS and GCC behaves differently.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文