4 ms·
> This construct works perfectly fine in C Intuitively, I would say that this is actually undefined behavior (it would probably be difficult to expose a wrong
by rom1v 2y ago
> This construct works perfectly fine in C
Intuitively, I would say that this is actually undefined behavior (it would probably be difficult to expose a wrong behavior in practice though).
In C specs, I found 6.5.2.2, paragraph 9:
> If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.
We might discuss whether
void (*)(char *)
is "compatible" with
void (*)(void *)
but I think it isn't, since:
void target(void *ptr) {}
void (*name)(char *ptr) = target;
fails to compile with the error message:
initialization of ‘void (*)(void *)’ from incompatible pointer type ‘void (*)(char *)’
The compiler explicitly says "incompatible pointer type".
Same for:
void target(char *ptr) {}
void (*name)(void *ptr) = target;
- pistoleer 2y agoIt's worse than that. This guy takes a void* function and casts it to a char* function, then passes it a char**. void (*name)(char *ptr); typedef void (*name_func)(char *ptr); void target(void *ptr) { printf("Input %p\n", ptr); } char *data = "string"; name = (name_func)target; // Illegal: casting fn that takes void* to a fn that takes char* name(&data); // Illegal: passing a char** into a function that takes char* Before someone mentions qsort(): the comparator function really is supposed to take a void*, and inside the function, you re-cast the void* argument to a pointer type of your desire. If you don't do it in that order, you're using it wrong.
- rom1v 2y ago> name(&data); // Illegal: passing a char* into a function that takes char* I assume this is just a typo in the article. He probably meant `name(data)`.
- trealira 2y agoIronically, in K&R, they did exactly this for casting comparator functions for their own version of qsort. /* declarations */ void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *)); int numcmp(char *, char *); /* the offending line */ qsort((void **) lineptr, 0, nlines-1, (int (*)(void*,void*)(numeric ? numcmp : strcmp));
- layer8 2y agoThe C standard mandates that a “pointer to void shall have the same representation and alignment requirements as a pointer to a character type”. In consequence, the same holds for an array of void pointers vs. an array of char pointers. The code therefore seems valid to me, and furthermore at no point is a function called with an argument type different from its formal parameters. In the GP example, a char** is passed where a char* is expected. That is clearly invalid.
- trealira 2y agoI was more referring to how, in the K&R example, a function of type "int (*)(char *, char *)" is cast to "int (*)(void *, void *)", in contradiction to what they said here: > Before someone mentions qsort(): the comparator function really is supposed to take a void*, and inside the function, you re-cast the void* argument to a pointer type of your desire. If you don't do it in that order, you're using it wrong. In contrast, the K&R example is an example of explicitly undefined behavior in the C standard: The behavior is undefined in the following circumstances: - A pointer is used to call a function whose type is not compatible with the pointed-to type (6.3.2.3).
- layer8 2y agoAh, I missed that. However, footnote 41 states “The same representation and alignment requirements are meant to imply interchangeability as arguments to functions […]”, which could be taken as implying compatibility of function types, though the normative wording doesn’t directly support that.
- trealira 2y agoGood point. It would be nice if the C standard were slightly clearer here.
- Someone 2y ago> the K&R example is an example of explicitly undefined behavior in the C standard You can’t blame K&R C for that. It predates “the C standard” by over a decade (1978 vs 1989) When it was written, what K&R said was C.
- deleted 2y ago[deleted]
- flohofwoe 2y agoIn C, all pointers are compatible to a void pointer without casting though (e.g. assigning a char pointer to a void pointer or the other way around is completely legal - I think that was the main reason why void pointers were added in the first place - it's basically an 'any pointer'). It's only C++ where this is an error (which is weird on its own, why even allow void pointers in C++ when the only reason they (presumably) exist doesn't work anymore). The above code still doesn't compile in C because C complains about the function signatures being incompatible, even though the only difference is the argument type which itself is 'equivalent' in C - which I guess could be interepreted as an edge case in the C spec? (IMHO the function prototypes should actually be compatible).
- deleted 2y ago[deleted]
- shakna 2y agoNot all pointers. Function pointers, though often used that way, should not be cast to void. ISO C forbids it. (Implementations may allow it.)
- gpderetta 2y agoISO C forbids it, but, FWIW, POSIX requires it. So pick your standard I guess.
- mmaniac 2y ago"Compatibility" has a specific meaning in C which isn't the same as what you're describing here. What you're talking about is implicit conversions without narrowing. Compatibility is essentially about ABI: https://en.cppreference.com/w/c/language/type#Compatible_types https://en.cppreference.com/w/c/language/type#Compatible_typ... The C standard is quite loose about pointer requirements. Conversions between data pointers or between function pointers are allowed, but conversions between each other are not guaranteed. You can read the nitty gritty here: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf POSIX defines stricter requirements for pointers than the C standard. All pointers are compatible, so they have the same size and representation, and conversions between function pointers and void* are explicitly allowed.
- nwellnhof 2y ago> it would probably be difficult to expose a wrong behavior in practice though Emscripten breaks if you cast function pointers: https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html https://emscripten.org/docs/porting/guidelines/function_poin...
- flohofwoe 2y agoGood point, interesting that I never ran into this issue in real world code bases so far (and I build a lot of code with Emscripten) :)
- layer8 2y agoYes. Simply put, void pointers and struct pointers (the actual breaking example in Curl) are not guaranteed to have the same byte-level representation, by the C standard. (For example, a struct pointer may always be aligned and therefore use fewer bits than a void pointer.) Passing one to a function that expects the other may result in arbitrary breakage.