Rendered at 23:35:26 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
chasil 21 hours ago [-]
I just rebuilt a core program that talks to my UNIVAC OS2200 CITA and my VAX ACMS from Linux.
I built this first as a 32-bit binary, and there was noise that I had to cut with a substr function.
In the rebuilding with my MinGW 64-bit Windows cross compiler on Oracle Linux, I had to mine function prototypes out of all the code for exactly this reason.
'for those unaware: c89 has a cool "feature" where, if you try to call a function which doesn't exist, rather than erroring out, the function is implicitly declared as a function with unspecified parameters returning int.'
I'm not testing this, because that is agony.
I'm leaving them a working build, and retiring this month.
This code was written by the living and the dead, and my stewardship is finished. I do not envy the poor soul who must test this.
It was ashes when I excavated it myself. A hat tip to those who have gone before.
geocar 15 hours ago [-]
> I had to mine function prototypes out of all the code for exactly this reason.
The program that does this is called cproto and it was available back on comp.sources.unix as early as 1989 (if you find yourself with a time machine) and these days is available on nearly every Linux distro.
Current GCC and clang error on them by default now, but that's a rather recent change.
uecker 9 hours ago [-]
GCC warns by default since version 5, released more than a decade ago.
chasil 6 hours ago [-]
My production binary was built on Oracle Linux 5, with gcc 4.1.2.
uecker 5 hours ago [-]
That version would at least warn with -Wall.
pjmlp 16 hours ago [-]
That feature was already present in K&R C, C89 function prototypes were actually taken from the C++ standardisation process back into C, while keeping that behaviour in place.
bellowsgulch 4 days ago [-]
All the weird stuff I have read about C or C++ doing over the years could be summed up with “Well… don’t do that.”
Like almost without fail. I can’t even think of how you’d end up in a scenario like this because a compiler would complain before letting you use an undefined implicit declaration.
mjevans 19 hours ago [-]
I sum it up with: give me an error message rather than undefined or unintuitive behavior.
bitbasher 4 days ago [-]
That doesn't make it any less interesting :)
stinkbeetle 17 hours ago [-]
It does. Some people would still be interested but lots of people would not. The net interestingness would certainly decrease.
Joel_Mckay 19 hours ago [-]
Most people build C in g++ before gcc, as it tends to have a better chance of catching most oddities. Then run it past valgrind to double check if something looks suspect or is slowly leaking. =3
flohofwoe 17 hours ago [-]
> Most people build C in g++ before gcc
That's not possible for 'standard C' though, only for the separate language that's called "the common subset of C and C++". E.g. it works more or less by accident for some specific C code that predates C99, but even that is built with C++ semantics (which has many subtle and some not-so-subtle differences to C semantics).
Instead of trying to build C in C++ mode, turn all the warnings to 11 (and also include warnings that aren't in -Wall -Wextra, like -Wsign-conversion).
pjmlp 16 hours ago [-]
Agreed, the point of divergence is already C89, not C99.
The C++ compatibility with C, is "As Close as Possible to C, but no Closer", meaning supporting C should not break the stronger type checking or the improved type system C++ has over C.
As such, besides C89 original differences, compatibility has only been added for C features that don't clash with C++ approach to the same problem, and while the C++ standard library keeps up with ISO C, it also does so taking into consideration C++ features for the implementation of said library functions.
The 'g++ -x c ...' will parse portable source just fine (compiles code as C, but links against libstdc++), and tends to identify poorly structured code with verbose checks. If people default to gcc file extension inference, than it is likely to remain silent when people do something silly.
Tbh I would be very surprised if `g++ -x c` and `gcc` use different language frontends.
One silly side effect of compiling C in C++ mode is that it forces you to cast from void pointers. E.g. the famous example of:
bla_t* bla = (bla_t*)malloc(sizeof(bla_t));
That extra cast is just a silly and completely pointless C++-ism. It's only the tip of the iceberg though (another difference was zero-initialization with `{}` vs `{0}`, but that's harmonized now in C23 so that `{}` is also valid in C, but as soon as you want to use C99 designated init, the 'harmonization' ends).
One critical missing piece of C compilation mode was usually that the compiler didn't warn when using a wrong enum type, but that's also fixed by now (assuming `-Wall -Wextra`, but that should always be enabled anyway, otherwise you're just flying blind), e.g.:
So yes, do not try to compile C code using a C++ compiler.
That you somehow get superior type checking with C++ is not really true anymore, as essentially all important parts were integrated into the C standard and the remaining should all exist as optional warnings in gcc. If you feel something is missing, please file a bug.
Joel_Mckay 7 hours ago [-]
The key logical jump here is "should", but in practice standards compliant C may still behave in unintuitive ways in edge-cases. It is not a compiler "bug" if functioning as defined, but rather a self-discipline issue to avoid shitty coding practices. =3
wahern 3 hours ago [-]
Just because a bit of C code compiles without diagnostics as C++ does not mean the code is safe. C++ does not have compound literals, but the syntax for a compound literal is accepted in several C++ compilers, including GCC and clang. However, in C compound literals have block-scoped lifetime, whereas C++ compilers give them expression-scoped lifetime. This means you can have silent use-after-free issues when using a C++ compiler. And, yes, I have encountered this (after diagnosing some read and write overflows in Valgrind and ASAN) and now try not to use compound literals in header files unless guarded to prevent accidental use with C++ compilers), though unfortunately this was a one of my later hard earned lessons.
Joel_Mckay 54 minutes ago [-]
Indeed, every C compiler including gcc is slightly different. Especially in the embedded space where pseudo-standards become a small subset of GNU compatible code very quickly. To be honest, gcc became the unofficial standard even with the GNU extensions.
Good C follows the 10 rules, uses old compatible macro language features, and is boring to trivially port. The "hold my beer" folks often go back to Python where the abstractions hide the foot-guns.
What specific examples do you have in mind where you would get a useful warning with a C++ compiler that you can not also get for C with gcc?
Joel_Mckay 4 hours ago [-]
It had to do with the lack of helpful warning/error messages while porting over a C project. Switching out for libstdc++ was helpful in narrowing the scope of where to look for the issue, and a better idea of the failure mode.
Given the Sealioning problem on YC, trying to remember an edge case issue from years ago would not go well. If your work habits aren't pushing into the compiler standards gray areas, than one probably won't ever encounter such issues. ymmv =3
I’m not going to defend all of the ones C(++) has but stuff like deref’ing null or integer overflow or out of bounds access has its roots in sanity, it just looks insane today. There are tools for many/most of these and if you’re not using them you’re doing it wrong.
flohofwoe 14 hours ago [-]
I'm not sure what the "which will never be fixed" part in the title is about, the problem was clearly fixed in C99 by removing the entire "misfeature" from the standard (e.g. GCC and Clang do error out both in C and C++ compilation mode).
My guess is that the implicit declaration was a leftover from a C predecessor (B?) and kept in C to simplify programming in hybrid B/C environments. Just a guess though.
15 hours ago [-]
guenthert 4 days ago [-]
"because the problematic behavior was removed from the standard over 27 years ago, we'll never have closure on what the intended interpretation is."
Obsolete standard turns out to be insufficiently strict, news at 11.
inigyou 22 hours ago [-]
newc at 11, too. it was called c11.
avadodin 15 hours ago [-]
op implies the issues in c89 were fixed with c99 not c11.
it may seem like a slightly more ridiculous claim, given the infamous features added in c99, although, in hindsight, every version after c89 was less C–like than the previous.
inigyou 12 hours ago [-]
it's just a silly pun. Also if you think C89 was peak, I encourage you to build a little ecosystem around it again and share it on HN.
That's a nice bit of understated 90s design right there
tosti 17 hours ago [-]
90s design had frames, tiled backgrounds, low contrast and lots of different font colors.
The "nice" sites didn't style much at all, which usually meant the default serif font was used.
kevin_thibedeau 22 hours ago [-]
> f[...]
It is illegal to perform pointer arithmetic on function pointers. They are not necessarily interchangeable with data pointers. Clang is wrong.
phire 21 hours ago [-]
In c89, there doesn't seem to be any distinction between pointer to functions and pointer to data.
Do you know what c89 does explicitly say is undefined? Any pointer arithmetic at all (or array subscripting) for pointers are not pointing to a member of an array. It's already invalid to do pointer arithmetic a pointer to a struct.
Not that it matters for this. This isn't doing pointer arithmetic, f is simply the identifier for the array of "sizeof(f) ints" that make up the function argument. The identifier is optional for a function declaration.
kevin_thibedeau 21 hours ago [-]
There is a distinction. Lisp machines do not allow function pointers to become data pointers. The C standard accounts for that. Function pointers also have special decay behavior unlike data pointers. You can chain an unlimited number of '*'s and still get a valid function pointer.
int f(int x) { return x + 1; }
int main(int argc, char *argv[]) {
int (*fp)(int) = f;
printf("Answer: %d\n", fp(39) + (****fp)(1));
return 0;
}
phire 20 hours ago [-]
Not sure about later versions, but c89 doesn't seem to forbid casting a function pointer to a data pointer (it does say that casting pointers has implementation defined aspects, so lisp machines aren't out of spec if they fail).
The reason you can chain unlimited ''s is that f isn't actually a function pointer, it's a function designator. Function designators are automatically converted to function pointers in almost every case, but '' is special cased so that indirecting a raw function designator results in another function designator.
Edit: Actually on a closer reading, maybe it does. C89 doesn't explicitly point it out as forbidden or undefined, it simply defines two classes (objects and functions) and only defines a very limited set of valid operations on pointers to functions.
Though one operation that is implementation defined is converting a pointer to an int, and an int to a pointer. So with two steps through an implementation defined path, you have a data pointer to a function.
geocar 15 hours ago [-]
void*
and
void(*)()
are just not guaranteed to be the same size. If the latter is larger then those "implementation defined aspects" are what kicks in. It's nothing to do with the int.
In some systems functions are smaller, since they're gate addresses in a jump table (who has more than 64k functions?), so that's no problem.
But in some systems functions are bigger, and PCDOS systems running in real mode this was very common, and in those cases you'd need to get the extra bits from someplace else (union trick), or just design things another way.
inigyou 13 hours ago [-]
In 1989 compilers weren't adversarial yet, and language specs weren't expected to be complete formal descriptions. The complete formal description was "whatever the compiler does" and "whatever the machine does" which was usually something sensible, unlike today.
malkia 18 hours ago [-]
This... from the time when kids played with real toys :)
jibal 15 hours ago [-]
I have a 30 page document of errors and ambiguities in the C89 standard -- I joined X3J11 with the intention to present it as a "Monday paper" (papers from committee members submitted on the first meeting day) but I couldn't get a flight and didn't arrive until Tuesday, and they (PJ Plauger specifically) wouldn't accept the paper.
At the next meeting I nevertheless voted to accept the standard. (Being first in alphabetical order, I was the first person to ever do so.)
Neywiny 9 hours ago [-]
Those 2 paragraphs seem counter to eachother. Not to judge, but was the claim of being the first to vote yes impactful in your decision?
jibal 8 hours ago [-]
That the standard had numerous flaws wasn't sufficient reason to vote against it, especially since I couldn't do anything about it since the committee wasn't going to consider my criticisms. Standardizing C had many beneficial consequences and not doing so would have been costly.
And it's not merely a "claim" -- are you actually suggesting that I'm lying? Your question doesn't even make sense as written, as my claim today could not have affected a decision in 1988. And "Not to judge, but" is a bizarre thing to say if one isn't judging. Your whole comment is rather rude.
Did the fact that I was the first to vote have an effect? Yes ... I was an outsider, having only joined the committee one meeting prior to the vote, and I really didn't know the general attitude of the committee members, so I didn't know how the vote would go, putting me in an awkward position. As it turned out, it was unanimous.
No, I'm not actually suggesting you're lying. Claim meaning a thing you have. Not "you claim to be." Like staking your claim. Or a bank's claim on a house. Important for me to make sure I'm using the right word, not one definition on Merriam Webster contains doubt or accusation. In fact it's quite the opposite. In case you need to brush up on it https://www.merriam-webster.com/dictionary/claim . If it helps you, I meant it in the noun way not the verb way. Not "are you claiming to be...". I do see that some definitions of other dictionaries when used as a verb are not my intent, so that's why I put the "not to judge". It was there to guide the reader in benefit of the doubt
jibal 6 hours ago [-]
Well, that clarifies your intent but it's a very peculiar usage, and more properly it should have been "claim on", not "claim of". In any case, there was no need or point to the words "the claim of" ... "was being the first to vote yes impactful?" is quite adequate.
> In case you need to brush up on it
I have no such need.
> If it helps you, I meant it in the noun way not the verb way.
It doesn't help at all as it's a noun in both "your claim on first vote privilege" and "your claim that you voted first".
> not one definition on Merriam Webster contains doubt or accusation
You are quite mistaken. Both "to assert in the face of possible contradiction" (verb form) and "an assertion open to challenge" (noun form) very much "contains doubt".
Enough of this off topic discussion of grammar ... I won't respond again.
I built this first as a 32-bit binary, and there was noise that I had to cut with a substr function.
In the rebuilding with my MinGW 64-bit Windows cross compiler on Oracle Linux, I had to mine function prototypes out of all the code for exactly this reason.
'for those unaware: c89 has a cool "feature" where, if you try to call a function which doesn't exist, rather than erroring out, the function is implicitly declared as a function with unspecified parameters returning int.'
I'm not testing this, because that is agony.
I'm leaving them a working build, and retiring this month.
This code was written by the living and the dead, and my stewardship is finished. I do not envy the poor soul who must test this.
It was ashes when I excavated it myself. A hat tip to those who have gone before.
The program that does this is called cproto and it was available back on comp.sources.unix as early as 1989 (if you find yourself with a time machine) and these days is available on nearly every Linux distro.
Also in 1989, mkproto from comp.sources.misc
http://ftp.fi.netbsd.org/pub/misc/archive/comp.sources.misc/...
Current GCC and clang error on them by default now, but that's a rather recent change.
Like almost without fail. I can’t even think of how you’d end up in a scenario like this because a compiler would complain before letting you use an undefined implicit declaration.
That's not possible for 'standard C' though, only for the separate language that's called "the common subset of C and C++". E.g. it works more or less by accident for some specific C code that predates C99, but even that is built with C++ semantics (which has many subtle and some not-so-subtle differences to C semantics).
Instead of trying to build C in C++ mode, turn all the warnings to 11 (and also include warnings that aren't in -Wall -Wextra, like -Wsign-conversion).
The C++ compatibility with C, is "As Close as Possible to C, but no Closer", meaning supporting C should not break the stronger type checking or the improved type system C++ has over C.
As such, besides C89 original differences, compatibility has only been added for C features that don't clash with C++ approach to the same problem, and while the C++ standard library keeps up with ISO C, it also does so taking into consideration C++ features for the implementation of said library functions.
"Sibling Rivalry: C vs C++"
https://www.stroustrup.com/sibling_rivalry.pdf
"C and C++: Siblings"
https://www.stroustrup.com/siblings_short.pdf
"C and C++, a case for compatibility"
https://www.stroustrup.com/compat_short.pdf
Two examples of the library updates,
"C++17 should refer to C11 instead of C99"
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p00...
"C++26 should refer to C23 not C17"
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p33...
Best of luck, =3
https://www.man7.org/linux/man-pages/man1/g++.1.html
One silly side effect of compiling C in C++ mode is that it forces you to cast from void pointers. E.g. the famous example of:
That extra cast is just a silly and completely pointless C++-ism. It's only the tip of the iceberg though (another difference was zero-initialization with `{}` vs `{0}`, but that's harmonized now in C23 so that `{}` is also valid in C, but as soon as you want to use C99 designated init, the 'harmonization' ends).One critical missing piece of C compilation mode was usually that the compiler didn't warn when using a wrong enum type, but that's also fixed by now (assuming `-Wall -Wextra`, but that should always be enabled anyway, otherwise you're just flying blind), e.g.:
https://www.godbolt.org/z/h5s4qWoTh
So yes, do not try to compile C code using a C++ compiler.
That you somehow get superior type checking with C++ is not really true anymore, as essentially all important parts were integrated into the C standard and the remaining should all exist as optional warnings in gcc. If you feel something is missing, please file a bug.
Good C follows the 10 rules, uses old compatible macro language features, and is boring to trivially port. The "hold my beer" folks often go back to Python where the abstractions hide the foot-guns.
https://en.wikipedia.org/wiki/The_Power_of_10:_Rules_for_Dev...
Given the Sealioning problem on YC, trying to remember an edge case issue from years ago would not go well. If your work habits aren't pushing into the compiler standards gray areas, than one probably won't ever encounter such issues. ymmv =3
https://www.youtube.com/watch?v=T4Upf_B9RLQ
https://www.youtube.com/watch?v=X6WHBO_Qc-Q
I’m not going to defend all of the ones C(++) has but stuff like deref’ing null or integer overflow or out of bounds access has its roots in sanity, it just looks insane today. There are tools for many/most of these and if you’re not using them you’re doing it wrong.
My guess is that the implicit declaration was a leftover from a C predecessor (B?) and kept in C to simplify programming in hybrid B/C environments. Just a guess though.
Obsolete standard turns out to be insufficiently strict, news at 11.
it may seem like a slightly more ridiculous claim, given the infamous features added in c99, although, in hindsight, every version after c89 was less C–like than the previous.
That's a nice bit of understated 90s design right there
The "nice" sites didn't style much at all, which usually meant the default serif font was used.
It is illegal to perform pointer arithmetic on function pointers. They are not necessarily interchangeable with data pointers. Clang is wrong.
Do you know what c89 does explicitly say is undefined? Any pointer arithmetic at all (or array subscripting) for pointers are not pointing to a member of an array. It's already invalid to do pointer arithmetic a pointer to a struct.
Not that it matters for this. This isn't doing pointer arithmetic, f is simply the identifier for the array of "sizeof(f) ints" that make up the function argument. The identifier is optional for a function declaration.
The reason you can chain unlimited ''s is that f isn't actually a function pointer, it's a function designator. Function designators are automatically converted to function pointers in almost every case, but '' is special cased so that indirecting a raw function designator results in another function designator.
Edit: Actually on a closer reading, maybe it does. C89 doesn't explicitly point it out as forbidden or undefined, it simply defines two classes (objects and functions) and only defines a very limited set of valid operations on pointers to functions.
Though one operation that is implementation defined is converting a pointer to an int, and an int to a pointer. So with two steps through an implementation defined path, you have a data pointer to a function.
In some systems functions are smaller, since they're gate addresses in a jump table (who has more than 64k functions?), so that's no problem.
But in some systems functions are bigger, and PCDOS systems running in real mode this was very common, and in those cases you'd need to get the extra bits from someplace else (union trick), or just design things another way.
At the next meeting I nevertheless voted to accept the standard. (Being first in alphabetical order, I was the first person to ever do so.)
And it's not merely a "claim" -- are you actually suggesting that I'm lying? Your question doesn't even make sense as written, as my claim today could not have affected a decision in 1988. And "Not to judge, but" is a bizarre thing to say if one isn't judging. Your whole comment is rather rude.
Did the fact that I was the first to vote have an effect? Yes ... I was an outsider, having only joined the committee one meeting prior to the vote, and I really didn't know the general attitude of the committee members, so I didn't know how the vote would go, putting me in an awkward position. As it turned out, it was unanimous.
Oh, and I found documentation: https://www.open-std.org/JTC1/sc22/wg14/www/docs/n062.pdf -- the roll call lists Balter (me) first.
> In case you need to brush up on it
I have no such need.
> If it helps you, I meant it in the noun way not the verb way.
It doesn't help at all as it's a noun in both "your claim on first vote privilege" and "your claim that you voted first".
> not one definition on Merriam Webster contains doubt or accusation
You are quite mistaken. Both "to assert in the face of possible contradiction" (verb form) and "an assertion open to challenge" (noun form) very much "contains doubt".
Enough of this off topic discussion of grammar ... I won't respond again.