我正在使用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:
编译行非常简单:
g ++ -Wall -fmessage-length = 0 -g -pthread
你们中许多人建议传递(void *)0。这当然会起作用。功能在很多地方使用的问题,我的意思是很多地方。我想找到能给我C ++标准所承诺的解决方案 - 8字节大小的NULL。
答案 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表现不同。