使用%p转换说明符打印空指针是不确定的行为?
#include <stdio.h>
int main(void) {
void *p = NULL;
printf("%p", p);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这个问题适用于C标准,而不适用于C实现.
通过在结构类型中使用灵活的数组成员(FAM),我们是否将程序暴露给未定义行为的可能性?
程序是否可以使用FAM并且仍然是严格符合的程序?
灵活数组成员的偏移量是否需要位于结构的末尾?
这些问题适用于C99 (TC3)和C11 (TC1).
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
int main(void) {
struct s {
size_t len;
char pad;
int array[];
};
struct s *s = malloc(sizeof *s + sizeof *s->array);
printf("sizeof *s: %zu\n", sizeof *s);
printf("offsetof(struct s, array): %zu\n", offsetof(struct s, array));
s->array[0] = 0;
s->len = 1;
printf("%d\n", s->array[0]);
free(s);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出:
sizeof *s: 16
offsetof(struct s, array): 12
0
Run Code Online (Sandbox Code Playgroud) 是否所有指针都是从指向结构类型的指针派生的问题都不容易回答.我发现这是一个重要问题,主要有以下两个原因.
A.缺少指向"任何"不完整或对象类型的指针,对方便的函数接口施加了限制,例如:
int allocate(ANY_TYPE **p,
size_t s);
int main(void)
{
int *p;
int r = allocate(&p, sizeof *p);
}
Run Code Online (Sandbox Code Playgroud)
[ 完整代码示例 ]
指向"任何"不完整或对象类型的现有指针明确描述为:
C99/ C11 §6.3.2.3 p1:
指向void的指针可以转换为指向任何不完整或对象类型的指针.[...]
从指向"任何"不完整或对象类型的现有指针派生的指针,指向void的指针,严格地是指向void的指针,并且不需要使用从指向"任何"不完整的指针派生的指针进行转换或对象类型.
B.程序员根据他们对特定实现的经验,使用基于不需要的假设的约定,有意识地或不知不觉地与指针的泛化相关,这种情况并不少见.假设,例如可转换,可表示为整数或共享公共属性:对象大小,表示或对齐.
根据C99 §6.2.5 p27/ C11 §6.2.5 p28:
[...]所有指向结构类型的指针应具有相同的表示和对齐要求.[...]
其次是C99 TC3 Footnote 39/ C11 Footnote 48:
相同的表示和对齐要求意味着可互换性作为函数的参数,函数的返回值和联合的成员.
虽然标准没有说:"指向结构类型的指针"并且选择了以下单词:"所有指向结构类型的指针",但它没有明确指定它是否适用于这种指针的递归推导.在标准中提到指针的特殊属性的其他情况下,它没有明确指定或提及递归指针派生,这意味着"类型派生"适用,或者它没有 - 但它没有明确提到.
虽然在引用类型时使用"所有指针"的措辞只使用两次,(对于结构和联合类型),而不是更明确的措辞:在整个标准中使用的"指针",我们不能总结它是否适用于这种指针的递归推导.
通常使用隐式函数返回void*转换为分配指定指针,就像malloc()一样:
void *malloc(size_t size);
int *pi = malloc(sizeof *pi);
Run Code Online (Sandbox Code Playgroud)
我希望在传递目标指针的地址时执行相同的赋值,而不从函数内部(不在其体内,也不在参数中)显式地转换其类型.
以下代码似乎实现了这一点.
.
#include <stdio.h>
#include <stdlib.h>
int allocate_memory(void *p, size_t s) {
void *pv;
if ( ( pv = malloc(s) ) == NULL ) {
fprintf(stderr, "Error: malloc();");
return -1;
}
printf("pv: %p;\n", pv);
*((void **) p) = pv;
return 0;
}
int main(void) {
int *pi = NULL;
allocate_memory(&pi, sizeof *pi);
printf("pi: %p;\n", (void *) pi);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
结果:
pv: 0x800103a8;
pi: 0x800103a8;
Run Code Online (Sandbox Code Playgroud) 根据两者C99 §6.2.5p27和C11 §6.2.5p28:
所有指向结构类型的指针都应具有相同的表示和对齐要求.
用脚注(#39和#48分别):
相同的表示和对齐要求意味着可互换性作为函数的参数,函数的返回值和联合的成员.
(注意,C89 §3.1.2.5没有指定关于结构的指针)
-
众所周知,指向void的指针,例如C11 §6.3.2.3p1:
A pointer to void may be converted to or from a pointer to any object type.
Run Code Online (Sandbox Code Playgroud)
并不意味着指向void的指针与指向void的指针相同,并不意味着与指向数据对象的指针的其他指针相同.
(如果我过于宽泛地使用"隐含相同"一词,我会道歉,它只能在指定的上下文中使用)
以下是演示指向struct的指针的通用指针的代码示例:
#include <stdio.h>
#include <stdlib.h>
int allocate_struct(void *p, size_t s) {
struct generic { char placeholder; };
if ( ( *(struct generic **) p = malloc(s) ) == NULL ) {
fprintf(stderr, "Error: malloc();");
return -1;
}
printf("p: %p;\n", …Run Code Online (Sandbox Code Playgroud)