【发布时间】:2019-07-31 10:32:08
【问题描述】:
昨晚我试图实现GLOB_ALTDIRFUNC,遇到了一个有趣的问题。
虽然在语义上可能略有不同,但(void *) 和(struct *) 类型是否等效?
示例代码:
typedef struct __dirstream DIR;
struct dirent *readdir(DIR *);
DIR *opendir(const char *);
...
struct dirent *(*gl_readdir)(void *);
void *(*gl_opendir)(const char *);
...
gl_readdir = (struct dirent *(*)(void *))readdir;
gl_opendir = (void *(*)(const char *))opendir;
...
DIR *x = gl_opendir(".");
struct dirent *y = gl_readdir(x);
...
我的直觉是这样说的;它们具有基本相同的存储/表示/对齐要求;并且它们对于参数和返回类型应该是等价的。
c99 standard 和 c11 standard 的6.2.5(类型) 和 6.7.6.3(函数声明符(包括原型)) 部分似乎证实了这一点.
所以下面的实现理论上应该是可行的:
现在我在 BSD 和 GNU libc 代码中看到了类似的事情,这很有趣。
这些转换的等价性是编译器实现工件的结果,还是可以从标准规范中推断出的基本限制/属性?
这会导致未定义的行为吗?
@nwellnhof 说:
为了使两种指针类型兼容,两者应相同 限定并且两者都应该是指向兼容类型的指针。
好的,这是关键。 (void *)和(struct *)怎么会不兼容?
从 6.3.2.3 指针: 指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再返回;结果应比较 等于原来的指针。如果使用转换后的指针调用 其类型与所指向的类型不兼容的函数, 行为未定义。
尚未确定。
进一步说明:
- 结构依赖于它们的第一个元素进行对齐,因此结构指针的对齐要求应该与 void 指针相同,对吧?
- 我最初没有在任何地方指定
DIR,但它保证是一个结构。 - 问题的全部意义在于知道我是否可以避免使用包装器(就像我为 gl_closedir 所做的那样,它的类型显然不兼容)。
- 虽然 C11/C99 可能不允许这样做,但实际上它被 BSD 和 GNU 系统代码使用,因此可能是其他一些相关标准,例如POSIX,指定行为。
这个特性中的例子:
- OpenBSD glob.c
-
glibc glob.h(ABI 不应该改变,由
_GNU_SOURCE控制 ->__USE_GNU)。
所以,到目前为止,我的想法是:
- 我想不出有什么理由不能将
(struct *)与(void *)交换,反之亦然。 -
struct将具有第一个元素的对齐方式,这可能是char,因此指针的要求与void指针的要求完全相同。 - 因此,
struct指针应具有与void指针相同的实现要求,而所有struct指针均等价的要求进一步强化了这一点。 - 所以
(const void *)应该等价于(const struct *) - 和
(void *)应该等同于(struct *) - 等等所有特殊属性。
【问题讨论】:
-
如果您将某些标签替换为“C”和“language-lawyer”标签,尤其是“C”标签,您可能会得到更好的响应。
-
您的问题含糊不清,您对什么等价感兴趣,并且某些代码可能隐藏在外部链接后面。请写简单的minimal reproducible example,这样我们就可以准确地看到答案了。
-
@user694733 谢谢,我应该添加更多代码...我认为这很明显,但也许不是。
-
请按照上一条评论的建议发布 MRE。
void *和struct X *不是兼容的类型。 glibc 是为 gcc 编译而编写的,它不假装可移植或符合标准 -
我添加了
readdir和opendir的声明以澄清问题。如果有错误,请编辑或回滚问题。
标签: c posix function-pointers c11 function-prototypes