If you're curious about my remark that the function typedef is useless, it's because you can't use it for definitions, just for declarations. You can do this:
typedef int functype(char* x);
functype myfunc;
...and it's essentially equivalent to:
int myfunc(char* x);
...but then you still need to define myfunc() "manually" by restating the parameters and the return value. This won't work:
I'd have to argue the function typedefs are not useless, I've come across two uses.
The obvious one is rather than a function pointer typedef, such the subsequent use in a struct is obviously a pointer. Which helps when others are initially reading unfamiliar structures.
The other case can be somewhat related, namely as an assertion / check when writing such handler functions, and more importantly updating them.
handler_ty some_handler;
int some_handler(int a) { /* ... */ }
When updating code, it allowed for easier to decode compiler errors if the expected type of handler_ty was changed, and some specific handler was incorrectly updated, or not updated at all.
Basically the error would generally directly call out the inconsistency with the prior line, rather than with the distanct use in the initialisation of 'table'.
As I recall this mechanism has been around since at least C89, I don't recall using it in K&R.
The first one is a use case for a function pointer type, which I don't dispute can be useful! The second one is interesting, although you gotta admit it would be more useful if we could just use the function type to define the function and avoid repetition or separate checks.
A typedef for a function type can't be used to define a function of the type, but it can be used to define a pointer to the function.
I tend to prefer not hiding pointers in typedefs. The type FILE in the standard is a good example; FILE is an object type, but code only uses values of type FILE*. The "*" acts as a reminder that you're dealing with pointers.
"Three expressions are still required by the compiler, but in C, there is an implicit semicolon after a function definition (void _() { })."
No, there's no implicit semicolon. The syntax for a for statement (as of C23) is:
for ( expression[opt] ; expression[opt] ; expression[opt] ) secondary-block
for ( declaration expression[opt] ; expression[opt] ) secondary-block
The second form allows a declaration (typically something like "int i = 0;" in place of the first expression. But there's no semicolon after the "declaration" term in the second form -- because most declarations provide their own semicolons. For example, "int i" is not syntactically a declaration, but "int i;" is.
gcc appears to be treating a function *definition* as a *declaration*. As far as I can tell, that's incorrect (though of course gcc can do anything it likes as an extension).
There was a change in this area between C17 and C23. C17 has a constraint: "The declaration part of a for statement shall only declare identifiers for objects having storage class auto or register." C23 dropped that constraint.
And even if we assume that a function definition is a declaration, the program still violates a constraint. If you compile with "-std=c23 -pedantic-errors", gcc complains "error: ISO C forbids nested functions".
Ah, yeah. I saw that part of the spec after discussing this example on Mastodon beforehand. But I explained it a lot worse than you did. By "implicit semicolon", I meant that a function definition doesn't require an explicit semicolon at the end. But that's a boneheaded and inaccurate way to express it.
Thanks for a proper analysis. I edited the text to better capture the gist of this, I think.
Have you looked at Duff's Device? It weaves a do/while loop into a switch statement. It's the earliest example of C language abuse I learned about (and it wasn't theoretical, Duff actually used it as an optimization).
Totally degen, loved it :) How come you are so familiar with all those arcane obscure specifications of the C standard? Had you been a business lawyer, you would be filthy rich right now, combining arcane loop-holes ad absurdum to get anything.
I think the basic explanation is that I write a fair amount of C, but almost all of it recreationally, so I have time to pause and wonder "what would happen if I do *that*"?
Once again, I'm learning new things about C! I got the typedef, but was unfamilar with either the GNU extension for needless puts declarations or the C99 BASIC feature :-) TIL! This series is fun, I'm looking forward to the next installment.
If you're curious about my remark that the function typedef is useless, it's because you can't use it for definitions, just for declarations. You can do this:
typedef int functype(char* x);
functype myfunc;
...and it's essentially equivalent to:
int myfunc(char* x);
...but then you still need to define myfunc() "manually" by restating the parameters and the return value. This won't work:
typedef int functype(char* x);
functype myfunc {
puts("Hello world");
}
I'd have to argue the function typedefs are not useless, I've come across two uses.
The obvious one is rather than a function pointer typedef, such the subsequent use in a struct is obviously a pointer. Which helps when others are initially reading unfamiliar structures.
typedef int handler_ty(int a);
struct foo {
handler_ty *handler;
/* ... */
}
struct foo table[] = { { /* init fields */, /* init fields */, };
The other case can be somewhat related, namely as an assertion / check when writing such handler functions, and more importantly updating them.
handler_ty some_handler;
int some_handler(int a) { /* ... */ }
When updating code, it allowed for easier to decode compiler errors if the expected type of handler_ty was changed, and some specific handler was incorrectly updated, or not updated at all.
Basically the error would generally directly call out the inconsistency with the prior line, rather than with the distanct use in the initialisation of 'table'.
As I recall this mechanism has been around since at least C89, I don't recall using it in K&R.
The first one is a use case for a function pointer type, which I don't dispute can be useful! The second one is interesting, although you gotta admit it would be more useful if we could just use the function type to define the function and avoid repetition or separate checks.
A typedef for a function type can't be used to define a function of the type, but it can be used to define a pointer to the function.
I tend to prefer not hiding pointers in typedefs. The type FILE in the standard is a good example; FILE is an object type, but code only uses values of type FILE*. The "*" acts as a reminder that you're dealing with pointers.
For example, you can write:
typedef int func_t(void);
int func(void) {
// ...
}
func_t *ptr = func;
"Three expressions are still required by the compiler, but in C, there is an implicit semicolon after a function definition (void _() { })."
No, there's no implicit semicolon. The syntax for a for statement (as of C23) is:
for ( expression[opt] ; expression[opt] ; expression[opt] ) secondary-block
for ( declaration expression[opt] ; expression[opt] ) secondary-block
The second form allows a declaration (typically something like "int i = 0;" in place of the first expression. But there's no semicolon after the "declaration" term in the second form -- because most declarations provide their own semicolons. For example, "int i" is not syntactically a declaration, but "int i;" is.
gcc appears to be treating a function *definition* as a *declaration*. As far as I can tell, that's incorrect (though of course gcc can do anything it likes as an extension).
There was a change in this area between C17 and C23. C17 has a constraint: "The declaration part of a for statement shall only declare identifiers for objects having storage class auto or register." C23 dropped that constraint.
And even if we assume that a function definition is a declaration, the program still violates a constraint. If you compile with "-std=c23 -pedantic-errors", gcc complains "error: ISO C forbids nested functions".
Ah, yeah. I saw that part of the spec after discussing this example on Mastodon beforehand. But I explained it a lot worse than you did. By "implicit semicolon", I meant that a function definition doesn't require an explicit semicolon at the end. But that's a boneheaded and inaccurate way to express it.
Thanks for a proper analysis. I edited the text to better capture the gist of this, I think.
Have you looked at Duff's Device? It weaves a do/while loop into a switch statement. It's the earliest example of C language abuse I learned about (and it wasn't theoretical, Duff actually used it as an optimization).
https://en.wikipedia.org/wiki/Duff%27s_device
Delightfully ç̶̼͇̘̱̫͋̂̇u̷̧͕̻̤̍̀̽̀̆͜r̸̪̦̣̥̣̆̈́̉̒̔̐̓ș̷͓̯̭͈͈͕̃̇ĕ̶̡̙̫̽͊̔̄ḑ̸̢͖̦͎̱̦̆. Love-hated it!
Totally degen, loved it :) How come you are so familiar with all those arcane obscure specifications of the C standard? Had you been a business lawyer, you would be filthy rich right now, combining arcane loop-holes ad absurdum to get anything.
I think the basic explanation is that I write a fair amount of C, but almost all of it recreationally, so I have time to pause and wonder "what would happen if I do *that*"?
Great mindset, keep it up
Once again, I'm learning new things about C! I got the typedef, but was unfamilar with either the GNU extension for needless puts declarations or the C99 BASIC feature :-) TIL! This series is fun, I'm looking forward to the next installment.