I was trying to implement GLOB_ALTDIRFUNC
last night, and tripped into an interesting question.
While maybe slightly semantically different, are (void *)
and (struct *)
types equivalent?
Example code:
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);
...
My intuition says so; they have basically the same storage/representation/alignment requirements; and they should be equivalent for arguments and return type.
Sections 6.2.5 (Types) and 6.7.6.3 (Function declarators (including prototypes)) of the c99 standard and the c11 standard seem to confirm this.
So the following implementation should in theory work:
Now I see similar things being done in BSD and GNU libc code, which is interesting.
Are the equivalence of these conversions a result of an implementation artifact from the compilers, or it is a fundamental restriction/property that can be inferred from the standard's specification?
Does this result in undefined behavior?
@nwellnhof said:
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
Ok, this is the key. How can (void *)
and (struct *)
be incompatible?
From 6.3.2.3 Pointers: A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.
Not yet determined.
Further clarification:
- Structs depend on their first element for alignment, so alignment requirements for a struct pointer should be the same as for void pointers, right?
- I didn't initially specify
DIR
anywhere, but it's guaranteed to be a struct. - The whole point of the question is to know if I can avoid wrappers (like the one I did for gl_closedir, whose type is clearly incompatible).
- While this may not be allowed by C11/C99, it is in practice used by BSD and GNU system code, so maybe some other relevant standard, e.g. POSIX, specifies the behavior.
Examples in the wild, in this very feature:
- OpenBSD glob.c
- glibc glob.h (ABI shouldn't change, controlled by
_GNU_SOURCE
->__USE_GNU
).
So, my thoughts so far are:
- I can't think of any reason you can't exchange a
(struct *)
with a(void *)
, and the other way around. - A
struct
would have the alignment of the first element, and this could be achar
so requirements of a pointer are exactly the same as forvoid
pointers. - So a
struct
pointer should have the same implementation requirements as avoid
pointer, which is further reinforced by the requirement for allstruct
pointers to be equivalent. - So
(const void *)
should be equivalent to(const struct *)
- and
(void *)
should be equivalent to(struct *)
- and so on with all special attributes.