当 NULL 不是全零位时,全零位指针值是否也'false'?
When NULL is not all-zero-bits, is an all-zero-bit pointer value also 'false'?
我知道 C 编译器不需要对 NULL
的位表示使用全零,但它们 * 是 * 标准所要求的 [= =12=] 在布尔值 contexts/comparisons 中评估为 false。因此下面程序中的2ndprintf
总是输出false
.
但我想知道的是:在 NULL
是 * 不是 * 的系统上,指针值 * 是* 所有零在布尔值 contexts/comparisons 中也计算为假?也就是说,下面程序中的1stprintf
会不会输出true
?
或者以稍微不同的方式提问:我可以依靠 calloc
来生成一个在布尔值 contexts/comparisons 中始终计算为 false 的指针值吗? this 问题的第一个答案使用 memset
清除名为 y
的 long*
的位,然后继续说 y==0
是 UB 因为 y
可能是“陷阱表示”(无论是什么)。 calloc
也只是清除位,所以也许 1st printf
中的 o->p
也是 UB?
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct { void * p; } obj;
int main() {
obj * o = calloc(sizeof(obj), 1);
assert(o); // assume successful allocation
printf("%s\n", o->p ? "true" : "false"); // 1st: could print "true"? Is o->p UB?
o->p = NULL;
printf("%s\n", o->p ? "true" : "false"); // 2nd: always prints "false"
return 0;
}
这个问题的第一个答案中对 NULL 和 0 进行了很好的讨论:What is the difference between NULL, '[=14=]' and 0?
该答案的重点是:
Note that what is a null pointer in the C language. It does not matter
on the underlying architecture. If the underlying architecture has a
null pointer value defined as address 0xDEADBEEF, then it is up to the
compiler to sort this mess out.
…Even on this funny architecture, the following ways are still valid
ways to check for a null pointer:
if (!pointer)
if (pointer == NULL)
if (pointer == 0)
在同一问题的第二个答案中……
A constant expression of type int with the value 0, or an expression
of this type, cast to type void * is a null pointer constant, which if
converted to a pointer becomes a null pointer. It is guaranteed by the
standard to compare unequal to any pointer to any object or function.
(简短回答,是的,您可以使用 if (!ptr)
检查 NULL 指针)。
typedef struct { void * p; } obj;
obj * o = calloc(sizeof(obj), 1);
assert(o); // Let us set aside the case of a failed allocation
printf("%s\n", o->p ? "true" : "false"); // 1st: could print "true" ?
can I rely on calloc
to produce a pointer value that will always evaluate to false in boolean contexts/comparisons?
否 - 输出可能是 "true"
.*1.
全零的位模式,作为指针,可能不是空指针。
7.22.3.2 The calloc function
2 The calloc function allocates space for an array of nmemb
objects, each of whose size is size. The space is initialized to all bits zero.301)
Footnote 301) Note that this need not be the same as the representation of floating-point zero or a null pointer constant.
示例:一个实现可能只有一个 空指针 编码,其位模式为全 1。 (void *)0
将全零位模式 int 0
转换为全一 void *
。 if (null_pointer)
始终为假,无论 空指针 .
的位模式如何
*1 但实际上是的,输出总是 "false"
。如今,不使用全零位模式作为 空指针 的实现并不常见。高度可移植的代码不会假设这种实用性。考虑一个旧的或新的新颖系统可能使用零位模式作为非 空指针 - 可悲的是破坏了许多假定全零位模式是 的代码库]空指针.
背景资料
考虑以下使用表达式逻辑值的地方,全部取自C18,我在粗斜体中强调:
6.3.1.2(布尔型)p1:任意标量值转换为_Bool
时,如果值比较,结果为0等于0;否则,结果为 1.
6.5.3.3(一元算术运算符)p5:如果其操作数的值为逻辑非运算符!
的结果为0比较不等于 0,如果其操作数 的值比较等于 0,则为 1。结果的类型为 int
。表达式 !E
等同于 (0==E)
.
6.5.13(逻辑与运算符)p3:如果 &&
运算符的两个操作数 不等于 0[,则 &&
运算符应产生 1;否则,它产生 0。结果的类型为 int
.
6.5.14(逻辑或运算符)p3:如果 ||
运算符的任一操作数 比较不等于 0[,则 ||
运算符应产生 1;否则,它产生 0。结果的类型为 int
.
6.5.15(条件运算符)p4:对第一个操作数求值;在它的评估和第二个或第三个操作数的评估(以评估者为准)之间有一个序列点。只有当第一个 比较不等于 0 时,才会评估第二个操作数;只有当第一个 比较等于 0 时,才会计算第三个操作数;结果是第二个或第三个操作数的值(以求值者为准),转换为下述类型。
6.8.4.1(if
语句)p2:在两种形式中,如果表达式 比较不等于 0,则执行第一个子语句。在 else
形式中,如果表达式 比较等于 0,则执行第二个子语句。如果通过标签到达第一个子语句,则不执行第二个子语句。
6.8.5 (Iteration statements) p4: 一个迭代语句导致一个叫做loop body的语句被重复执行直到控制表达式比较等于0。无论循环是否发生,都会发生重复
body 是从迭代语句或通过跳转输入的。
“E比较等于0”等价于C表达式(E == 0)
,“E比较不等于0”等价于C表达式(E != 0)
。相等运算符的约束由下式给出:
- 6.5.9(等式运算符)p2:满足以下条件之一:
- 两个操作数都是算术类型;
- 两个操作数都是指向兼容类型的限定或非限定版本的指针;
- 一个操作数是指向对象类型的指针,另一个是指向
void
的限定或非限定版本的指针;或
- 一个操作数是指针,另一个是空指针常量.
关于至少一个操作数是指针的相等运算符的语义:
6.5.9(相等运算符)p5:否则,至少有一个操作数是指针。如果一个操作数是指针而另一个是空指针常量,则将空指针常量转换为指针的类型。如果一个操作数是
指向对象类型的指针,另一个是指向 void
的限定或非限定版本的指针,前者转换为后者的类型。
p6: 两个指针比较相等当且仅当都是空指针,都是指向同一个对象(包括指向一个对象和其开头的子对象的指针)或函数的指针,两者是指向同一个数组对象的最后一个元素的指针,或者一个是指向一个数组对象末尾的指针,另一个是指向恰好紧跟在第一个数组之后的另一个数组对象的开头的指针地址 space.
中的对象
关于空指针常量:
- 6.3.2.3(指针)p3:值为 0 的整型常量表达式,或强制转换为类型
void *
的此类表达式,称为 空指针常量67)。如果将空指针常量转换为指针类型,则生成的指针称为 空指针,保证与指向任何对象或函数的指针比较不相等。
OP 的问题
But what I want to know is: on systems where NULL
is not all zeros, will a pointer value that is all zeros also evaluate to false in boolean contexts/comparisons?
旁白:NULL
是一个空指针常量,不一定是空指针(见上文6.3.2.3p3它可以是一个整数常量表达式)。你真正的意思是一个系统,其中空指针的位表示是并非全为零。
注意: 正如 Eric Postpischil 在下面的评论中指出的那样,一个系统可以有多个空指针值的位表示,因此我们假设 none它们是这个问题的 all-zero 位表示。
为了使布尔值 contexts/comparisons 中的指针值计算为 false,它必须 与 0 进行比较。在此上下文中,它必须比较不等于空指针常量。通过上面的 6.5.9p5,空指针常量将被转换为它所比较的指针的类型。根据上面的 6.5.9p6,空指针值不会与 non-null 指针值进行比较。因此,在空指针值不是所有位为零的系统上,所有位为零的 non-null 指针值将在布尔上下文中计算为真。
Or asked in a slightly different way: can I rely on calloc
to produce a pointer value that will always evaluate to false in boolean contexts/comparisons?
不,您不能依赖 calloc
(或字节值为 0 的 memset
)来生成在布尔上下文中计算为 false 的指针值。如果具有 all-zero 位表示的指针值不是空指针值,它将在布尔上下文中计算为真。
核心答案
But what I want to know is: on systems where NULL
is *not* all zeros, will a pointer value that *is* all zeros also evaluate to false in boolean contexts/comparisons?
在 C 实现中,C 标准允许以下任何一项:
- All-bits-zero 是一个空指针,没有其他位模式是。
- All-bits-zero 是一个空指针,一个或多个其他位模式是。
- All-bits-zero 不是空指针,一个或多个其他位模式是。
换句话说,C 实现可以将任何一个或多个位模式指定为空指针,这可能包括也可能不包括 all-bits-zero。 (如果 C 实现确实允许多个位模式为空指针,则必须确保它们比较相等。)
… will the 1st printf
in the program below ever output true
?
允许打印“true”; calloc
的结果是所有位为零的内存,并且将该内存解释为 void *
可能会导致指针值不是空指针值。
补充
… where NULL
is *not* all zeros…
NULL
只是源代码中的内容。它是 0
或 ((void *) 0)
或等效项。无论它在源代码中用作指针的什么地方(也就是说,您正在做像 if (pointer != NULL)
这样的正常事情,而不是像 int x = 3 + NULL;
这样的杂乱无章),编译器都会有效地将它转换为空指针。也就是说,如果 all-bits-zero 在 C 实现中不是空指针,编译器会将 pointer != NULL
编译为 pointer
与表示空指针的某些位模式的比较。
所以你的问题都是关于空指针的;他们不是关于 NULL
.
… on systems where…
什么是空指针的最终决定取决于 C 实现,而不是它执行的系统。 C 实现可以用任何它想要的方式表示指针,并在指令中使用机器地址时根据需要转换它们。
您可以使用显式和防御性编码风格来避免此类问题。
如果你有一个指针 _p,写这样的结构
(_p==NULL)?(A):(B)
现在任何 reader 立即知道,您的目的是检查 _p 是否等于 NULL,即使在 NULL 可能不同于整数值 0 的机器上,编译器也会自动正确执行.由于依赖隐式行为,静态代码检查器现在也不会警告您。
(_p)?(A):(B)
只是做得不对
但除此之外,这是一个有趣的技术问题。
C++ 委员会在 2019 年或 2020 年的一次有趣的谈话中透露,即使是这些人也在考虑放弃对一些奇怪的未定义行为的兼容性,这在 1970 年之前对于一些 3-4 架构来说是需要的。在过去的几十年里,这种东西没有任何已知的用途——至少据我所知。正如对您的问题的第一条评论所述:您几乎找不到任何有此类问题的机器 - 至少在博物馆之外。
我知道 C 编译器不需要对 NULL
的位表示使用全零,但它们 * 是 * 标准所要求的 [= =12=] 在布尔值 contexts/comparisons 中评估为 false。因此下面程序中的2ndprintf
总是输出false
.
但我想知道的是:在 NULL
是 * 不是 * 的系统上,指针值 * 是* 所有零在布尔值 contexts/comparisons 中也计算为假?也就是说,下面程序中的1stprintf
会不会输出true
?
或者以稍微不同的方式提问:我可以依靠 calloc
来生成一个在布尔值 contexts/comparisons 中始终计算为 false 的指针值吗? this 问题的第一个答案使用 memset
清除名为 y
的 long*
的位,然后继续说 y==0
是 UB 因为 y
可能是“陷阱表示”(无论是什么)。 calloc
也只是清除位,所以也许 1st printf
中的 o->p
也是 UB?
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct { void * p; } obj;
int main() {
obj * o = calloc(sizeof(obj), 1);
assert(o); // assume successful allocation
printf("%s\n", o->p ? "true" : "false"); // 1st: could print "true"? Is o->p UB?
o->p = NULL;
printf("%s\n", o->p ? "true" : "false"); // 2nd: always prints "false"
return 0;
}
这个问题的第一个答案中对 NULL 和 0 进行了很好的讨论:What is the difference between NULL, '[=14=]' and 0?
该答案的重点是:
Note that what is a null pointer in the C language. It does not matter on the underlying architecture. If the underlying architecture has a null pointer value defined as address 0xDEADBEEF, then it is up to the compiler to sort this mess out.
…Even on this funny architecture, the following ways are still valid ways to check for a null pointer:
if (!pointer)
if (pointer == NULL)
if (pointer == 0)
在同一问题的第二个答案中……
A constant expression of type int with the value 0, or an expression of this type, cast to type void * is a null pointer constant, which if converted to a pointer becomes a null pointer. It is guaranteed by the standard to compare unequal to any pointer to any object or function.
(简短回答,是的,您可以使用 if (!ptr)
检查 NULL 指针)。
typedef struct { void * p; } obj;
obj * o = calloc(sizeof(obj), 1);
assert(o); // Let us set aside the case of a failed allocation
printf("%s\n", o->p ? "true" : "false"); // 1st: could print "true" ?
can I rely on
calloc
to produce a pointer value that will always evaluate to false in boolean contexts/comparisons?
否 - 输出可能是 "true"
.*1.
全零的位模式,作为指针,可能不是空指针。
7.22.3.2 The calloc function
2 The calloc function allocates space for an array ofnmemb
objects, each of whose size is size. The space is initialized to all bits zero.301)
Footnote 301) Note that this need not be the same as the representation of floating-point zero or a null pointer constant.
示例:一个实现可能只有一个 空指针 编码,其位模式为全 1。 (void *)0
将全零位模式 int 0
转换为全一 void *
。 if (null_pointer)
始终为假,无论 空指针 .
*1 但实际上是的,输出总是 "false"
。如今,不使用全零位模式作为 空指针 的实现并不常见。高度可移植的代码不会假设这种实用性。考虑一个旧的或新的新颖系统可能使用零位模式作为非 空指针 - 可悲的是破坏了许多假定全零位模式是 的代码库]空指针.
背景资料
考虑以下使用表达式逻辑值的地方,全部取自C18,我在粗斜体中强调:
6.3.1.2(布尔型)p1:任意标量值转换为
_Bool
时,如果值比较,结果为0等于0;否则,结果为 1.6.5.3.3(一元算术运算符)p5:如果其操作数的值为逻辑非运算符
!
的结果为0比较不等于 0,如果其操作数 的值比较等于 0,则为 1。结果的类型为int
。表达式!E
等同于(0==E)
.6.5.13(逻辑与运算符)p3:如果
&&
运算符的两个操作数 不等于 0[,则&&
运算符应产生 1;否则,它产生 0。结果的类型为int
.6.5.14(逻辑或运算符)p3:如果
||
运算符的任一操作数 比较不等于 0[,则||
运算符应产生 1;否则,它产生 0。结果的类型为int
.6.5.15(条件运算符)p4:对第一个操作数求值;在它的评估和第二个或第三个操作数的评估(以评估者为准)之间有一个序列点。只有当第一个 比较不等于 0 时,才会评估第二个操作数;只有当第一个 比较等于 0 时,才会计算第三个操作数;结果是第二个或第三个操作数的值(以求值者为准),转换为下述类型。
6.8.4.1(
if
语句)p2:在两种形式中,如果表达式 比较不等于 0,则执行第一个子语句。在else
形式中,如果表达式 比较等于 0,则执行第二个子语句。如果通过标签到达第一个子语句,则不执行第二个子语句。6.8.5 (Iteration statements) p4: 一个迭代语句导致一个叫做loop body的语句被重复执行直到控制表达式比较等于0。无论循环是否发生,都会发生重复 body 是从迭代语句或通过跳转输入的。
“E比较等于0”等价于C表达式(E == 0)
,“E比较不等于0”等价于C表达式(E != 0)
。相等运算符的约束由下式给出:
- 6.5.9(等式运算符)p2:满足以下条件之一:
- 两个操作数都是算术类型;
- 两个操作数都是指向兼容类型的限定或非限定版本的指针;
- 一个操作数是指向对象类型的指针,另一个是指向
void
的限定或非限定版本的指针;或 - 一个操作数是指针,另一个是空指针常量.
关于至少一个操作数是指针的相等运算符的语义:
6.5.9(相等运算符)p5:否则,至少有一个操作数是指针。如果一个操作数是指针而另一个是空指针常量,则将空指针常量转换为指针的类型。如果一个操作数是 指向对象类型的指针,另一个是指向
void
的限定或非限定版本的指针,前者转换为后者的类型。p6: 两个指针比较相等当且仅当都是空指针,都是指向同一个对象(包括指向一个对象和其开头的子对象的指针)或函数的指针,两者是指向同一个数组对象的最后一个元素的指针,或者一个是指向一个数组对象末尾的指针,另一个是指向恰好紧跟在第一个数组之后的另一个数组对象的开头的指针地址 space.
中的对象
关于空指针常量:
- 6.3.2.3(指针)p3:值为 0 的整型常量表达式,或强制转换为类型
void *
的此类表达式,称为 空指针常量67)。如果将空指针常量转换为指针类型,则生成的指针称为 空指针,保证与指向任何对象或函数的指针比较不相等。
OP 的问题
But what I want to know is: on systems where
NULL
is not all zeros, will a pointer value that is all zeros also evaluate to false in boolean contexts/comparisons?
旁白:NULL
是一个空指针常量,不一定是空指针(见上文6.3.2.3p3它可以是一个整数常量表达式)。你真正的意思是一个系统,其中空指针的位表示是并非全为零。
注意: 正如 Eric Postpischil 在下面的评论中指出的那样,一个系统可以有多个空指针值的位表示,因此我们假设 none它们是这个问题的 all-zero 位表示。
为了使布尔值 contexts/comparisons 中的指针值计算为 false,它必须 与 0 进行比较。在此上下文中,它必须比较不等于空指针常量。通过上面的 6.5.9p5,空指针常量将被转换为它所比较的指针的类型。根据上面的 6.5.9p6,空指针值不会与 non-null 指针值进行比较。因此,在空指针值不是所有位为零的系统上,所有位为零的 non-null 指针值将在布尔上下文中计算为真。
Or asked in a slightly different way: can I rely on
calloc
to produce a pointer value that will always evaluate to false in boolean contexts/comparisons?
不,您不能依赖 calloc
(或字节值为 0 的 memset
)来生成在布尔上下文中计算为 false 的指针值。如果具有 all-zero 位表示的指针值不是空指针值,它将在布尔上下文中计算为真。
核心答案
But what I want to know is: on systems where
NULL
is *not* all zeros, will a pointer value that *is* all zeros also evaluate to false in boolean contexts/comparisons?
在 C 实现中,C 标准允许以下任何一项:
- All-bits-zero 是一个空指针,没有其他位模式是。
- All-bits-zero 是一个空指针,一个或多个其他位模式是。
- All-bits-zero 不是空指针,一个或多个其他位模式是。
换句话说,C 实现可以将任何一个或多个位模式指定为空指针,这可能包括也可能不包括 all-bits-zero。 (如果 C 实现确实允许多个位模式为空指针,则必须确保它们比较相等。)
… will the 1st
printf
in the program below ever outputtrue
?
允许打印“true”; calloc
的结果是所有位为零的内存,并且将该内存解释为 void *
可能会导致指针值不是空指针值。
补充
… where
NULL
is *not* all zeros…
NULL
只是源代码中的内容。它是 0
或 ((void *) 0)
或等效项。无论它在源代码中用作指针的什么地方(也就是说,您正在做像 if (pointer != NULL)
这样的正常事情,而不是像 int x = 3 + NULL;
这样的杂乱无章),编译器都会有效地将它转换为空指针。也就是说,如果 all-bits-zero 在 C 实现中不是空指针,编译器会将 pointer != NULL
编译为 pointer
与表示空指针的某些位模式的比较。
所以你的问题都是关于空指针的;他们不是关于 NULL
.
… on systems where…
什么是空指针的最终决定取决于 C 实现,而不是它执行的系统。 C 实现可以用任何它想要的方式表示指针,并在指令中使用机器地址时根据需要转换它们。
您可以使用显式和防御性编码风格来避免此类问题。
如果你有一个指针 _p,写这样的结构
(_p==NULL)?(A):(B)
现在任何 reader 立即知道,您的目的是检查 _p 是否等于 NULL,即使在 NULL 可能不同于整数值 0 的机器上,编译器也会自动正确执行.由于依赖隐式行为,静态代码检查器现在也不会警告您。
(_p)?(A):(B)
只是做得不对
但除此之外,这是一个有趣的技术问题。
C++ 委员会在 2019 年或 2020 年的一次有趣的谈话中透露,即使是这些人也在考虑放弃对一些奇怪的未定义行为的兼容性,这在 1970 年之前对于一些 3-4 架构来说是需要的。在过去的几十年里,这种东西没有任何已知的用途——至少据我所知。正如对您的问题的第一条评论所述:您几乎找不到任何有此类问题的机器 - 至少在博物馆之外。