5 ms·
How often do you need to do that? I feel like I never have, and I think feel like it's a rare enough use as that it shouldn't be the default behavior, but maybe
by dlbucci 7y ago
How often do you need to do that? I feel like I never have, and I think feel like it's a rare enough use as that it shouldn't be the default behavior, but maybe I haven't written enough C.
- saagarjha 7y agoIt shouldn't be the default, but I use it occasionally when you have a function pointer and need to call it with a set of arguments that you get at runtime.
- kazinator 7y agoThere isn't any way to do this in ISO C. Libraries like libffcall and libffi exist for dynamically building a C argument list and calling it. If we stick to standard C, all calls are statically determined, so your only option is to switch among different call expressions, like: switch (nargs) { case 0: return f(); case 1: return f(a[0]); case 2: return f(a[0], a[1]); ... } Here, if the f pointer is under-declared, it saves you some casting. You can use a union: struct fun { int nargs; union { int (*ptr0)(void); int (*ptr1)(valtype); int (*ptr2)(valtype, valtype); ... } fn; } Then: switch (f->nargs) { case 0: return f->fn.ptr0(); case 1: return f->fn.ptr1(arg[0]); ... } Now there is a modicum of type checking. When you create the "struct fun" object, you may be able to take the address of a function that is compatible with one of the ptr's, and assing it to the correct union member without having to use a cast. If you record the number of arguments correctly, it won't be misused.
- saagarjha 7y agoYes, I know this: I'm relying POSIX's guarantees and not what ISO C mandates (also truth be told I usually do this in Objective-C, but that doesn't actually change anything significantly). Unfortunately I get a function pointer and need forward essentially anything, so I'm not going to have any sort of type safety at all…
- kazinator 7y agoWhat guarantees does POSIX make about calling functions that are not in ISO C? I'm curious. POSIX generaly has very little to say about C matters; for the most part it defers to ISO C by normative reference. Off the top of my head, because POSIX specifies dlopen and dlsym, that pretty much requires function pointers have to have a common representation convertible between void * and back.
- ndesaulniers 7y agoFunction pointers in a union? Now that's a first (for me). One of my coworkers could defeat any idea for a new optimization with "well, what about unions?" which would typically complicate things quickly...
- saagarjha 7y ago> One of my coworkers could defeat any idea for a new optimization with "well, what about unions?" which would typically complicate things quickly... I'm not sure I follow: they would thwart your attempts at optimizing things by talking about unions?
- kazinator 7y agoCompilers that allow type punning through a union have to basically consider the access to a member of the union to have an effect on any other member. It's conceivable that there are edge cases where that consideration could hamper the optimization of a code which uses unions without perpetrating any sort of type punning. Hmm, like what? Say we have a really contrived function that works with two pointers A and B to the same union type. The compiler cannot prove that A and B are distinct. The function evaluates A->x = expr, and also B->y in several places. Since A and B might be the same pointer, the assignment has to be regarded as clobbering B->y, which interferes with CSE of B->y and register caching. As of C99, a possible solution here would be to declare the pointers restrict; then the compiler assumes they don't overlap: A->x has nothing to do with B->y. If a struct is used, then x and y are different members and have nothing to do with each other for that reason, needless to say, even if A and B are the same pointer.
- TeMPOraL 7y agoGiven that this is C, can the compiler prove in any case that two pointers are distinct? When you're dealing with a structure, it could still be the case that A = B + sizeof(structure.x) (+/- padding).
- kazinator 7y agoHere, the union is being used as a space-saving structure. We only need to store one pointer, but it can be of different types. We access the same one that we most recently stored. Most of the code surrounding this won't contain assignments into the union, so it won't impact optimization. In an interpreted language I wrote this kind of union is initialized when a function object comes to life, and then not mutated again. The union is necessary because a struct would blow up the size of the object significantly (and then it wouldn't fit into the GC heap cell size, requiring an additional piece of malloced memory).
- quietbritishjim 7y agoYour idea to use a union of function pointers also allows a much broader range of function parameter types to be used. When you call a function that used a K&R declaration, the arguments are subject to default promotion e.g. short to int, so you can't call "int f(short)" if it is only declared "int f()". Your idea doesn't have this problem.
- hermitdev 7y agoI have actually run across code in the wild along the lines of: int foo() int x; int y; { return x + y; } Perfectly legal C, if not an old archaic and seldom used syntax. I saw it in some proprietary code, so, no, I cannot provide a link. It was odd, but worked for what it was trying to do. It pays to know the language you work in, not to write things like this, but to understand on the off chance you encounter it in the wild. Edit, I may be wrong on the syntax, it might be: int foo(x, y) int x; int y; { return x + y; } I dont have the ISO spec in front of me, but one of these should work.
- deleted 7y ago[deleted]
- barrkel 7y agoThat's K&R C, predating ANSI C. It's not that uncommon if you're looking at long-lived codebases. Rather it should look like this: int foo(x, y) int x; int y; { return x + y; } x and y would be assumed to be int in the absence of the type specifiers, so in this case they're optional.
- klodolph 7y agoTo add to this, int is also default return type. foo(x, y) { return x + y; }
- mkehrt 7y agoint main() { return 0; } Main takes argc and argv, but it's idiomatic to omit them if unused.
- shawxe 7y agoWhy not just use int main(void)?
- deleted 7y ago[deleted]
- mkehrt 7y agoI'm confused. main doesn't take void--it takes two arguments. The f() syntax means that the definition doesn't specify how many arguments the function will be called with, while f(void) means it takes no arguments.
- pksadiq 7y agoAs per the standard, main() can take no argument [ie, main(void)] or 2 arguments [main (int, char asterisk asterisk) or equivalent] or some implementation defined manner. See 5.1.2.2.1 in C2x working draft[0]. [0] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2346.pdf http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2346.pdf
- tropo 7y agoWow, it is painful to see that they still haven't accepted case ranges. They are supported by gcc, clang, and icc. Like so: case 123 ... 456: Switching on strings is another thing people have been wanting for half a century. It would seep up many programs, because most programmers don't bother to generate a perfect hash or a carefully-balanced tree of "if".
- eridius 7y agoThis is irrelevant; this is the function definition, which means the compiler knows this function takes zero arguments. It works because with the way the C ABI works on all platforms I'm aware of, extra arguments passed to the function can be completely ignored by the function with no ill effects, so the fact that _start actually invokes main with argc and argv can be ignored by main. Which is to say, you could also write this as int main(void) { return 0; } and that would work equally as well.
- Crinus 7y agoI use it sometimes for callbacks that take similar but not exactly the same arguments, f.e. containers for pointers that can have an optional "release" function pointer that is called to release all pointers in the container and can be assigned to "free" for raw data, or object specific destructors like "free_bitmap" or "free_sound" or even "free_container" (so having a container with other containers inside that may have their own "release" functions).