134 points dverlaeckt80 4 days ago 118 comments
MisterTea 3 days ago | parent
I have a friend who is currently on the C committee and is likely going to leave because of all the bad ideas which have both been implemented and going to be implemented.
* No, spamming a million ugly #ifdefs all over the source files is gross and hard to read. Split up the code dammit.
201984 2 days ago | parent
Atomics are necessary nowadays though.
3836293648 2 days ago | parent
Not that I'd want it, I like complex type systems, but I see it as philosophically consistent with C.
publicmail 2 days ago | parent
aw1621107 2 days ago | parent
It's not just about atomics, but you might still find Hans Boehm's 2005 paper "Threads Cannot Be Implemented As a Library" [0] interesting. It describes (among other things) correctness and performance issues that can arise if the compiler and/or language don't know about thread/atomic semantics.
[0]: https://web.stanford.edu/class/archive/cs/cs240/cs240.1236/o...
Joker_vD 2 days ago | parent
adgjlsfhk1 2 days ago | parent
homosapien97 2 days ago | parent
dokyun 2 days ago | parent
> We explicitly do not address the less frequently used environments in which multiple concurrent threads communicate communicate primarily through message passing, such as C programs communicating only through the MPI[34] library, or Erlang[11] or Concurrent ML[28] programs.
Heh, there's your answer. CSP FTW!
gpderetta 2 days ago | parent
dokyun 1 day ago | parent
That's something pthreads do and CSP doesn't.
> you need a memory model with compiler support. Even if you exclusively use mutexes.
If you use CSP you can effectively eliminate shared memory in user space and you don't have to handle this with the compiler. Then you can say "here's a way to communicate between processes in user space, the runtime/kernel handles the shit underneath. If you want to do it some other way we can't guarantee it'll work."
201984 1 day ago | parent
dokyun 1 day ago | parent
https://en.wikipedia.org/wiki/Communicating_sequential_proce...
> but if you eliminate shared memory and have the kernel copy data between threads, you're leaving a ton of performance on the table.
If that's your bottleneck then you've either already solved every other class of performance issue with your program or you're working in a fantasy environment-- you're probably not hacking on FFmpeg right now. 90% of performance is I/O bound, whether it be the disk, the network or whatever. You can easily ascertain the correctness of a CSP system, because it's just message passing and inherently synchronous. Not so much with shared memory. It's a tradeoff. Unix pipes are also pretty goddamn fast, like, this is an issue people have spent time squeezing as much performance as possible out of. Google uses Go for all of their important backend shit. The entire internet is a giant message passing system.
gpderetta 1 day ago | parent
funny you say that, because to take advantage of the monstrous bandwidth offered by modern ethernet, OS bypass with the card DMAing directly in your process cache is a must. That reintroduces all the "fun" shared memory ordering issues, except trickier (you are talking with a PCI device, not just another core) and buggier.
Re disk, I'm sure someone will revive optane at some point, and we will be back with memory model issues in our disk I/O path.
The reality is that a programming language that claims to be usable for system programming must be able to deal with shared memory and provide a concurrent memory model.
dokyun 11 hours ago | parent
Architecture problem. Do your programs normally saturate a 40 gig network pipe?
> The reality is that a programming language that claims to be usable for system programming must be able to deal with shared memory and provide a concurrent memory model.
You can boot up a Plan 9 kernel and tell me if it works or not. Like, this is not a high bar, but you keep treating it like it is because it's based on the assumption that all compilers are broken with the set of all compilers being GCC.
gpderetta 1 day ago | parent
And of course any sufficiently complicated message passing program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of shared memory and coherency[1].
[1] Yes, the reverse is also true.
jcranmer 2 days ago | parent
And, FWIW, in a lot of high-performance code, the overhead of a function call may be too high for an atomic operation.
dokyun 2 days ago | parent
aw1621107 2 days ago | parent
dokyun 2 days ago | parent
That's an assumption that you're treating as though it somehow informs reality. I gave you an example of atomics being handled by a library, and the man page points to where in the source code it's defined.
> so you'd need to know how Plan 9's compiler/language specifies atomics.
It doesn't, as far as I can tell. Kencc is a very "dumb" compiler that does little magic nor does it extend the language beyond the standard C that the Bell Labs guys were used to. For whatever reasons, they found it expedient to write new libraries instead of changing the language itself. They did occasionally write compilers for new languages when they had wacky ideas that would be difficult to accomodate, like Newsqueak and Alef. In Alef's case they actually did accomodate some of it, which is how Plan 9 got libthread.
dzaima 2 days ago | parent
Of course no doubt that (hopefully) C compilers used for compiling plan 9 things won't break it, but...that's then talking about plan9-C, not C99, at which point you're necessarily already in the bucket of "this library relies on extra guarantees that plan9 C makes, instead of it just being a regular C99 library".
> > so you'd need to know how Plan 9's compiler/language specifies atomics.
> It doesn't, as far as I can tell. Kencc is a very "dumb" compiler
...So it effectively quietly makes a bunch of quiet guarantees that C99 doesn't, on account of not making certain valid optimizations. Even if not explicit, that's still semantics outside of those C99 specifies, that code may be relying on.
Fact of the matter is, you cannot make a full atomics library that will necessarily work correctly on C99. As a simple example I came up with on the spot:
static int foo;
int main() {
some_preparation();
foo = 2;
fence_probably();
do_thing(&foo);
}
A fully-proper standards-compliant C99 compiler could notice that foo isn't used until the `do_thing` call, and as such reorder the `foo = 2;` to after the `fence_probably` call (yay for putting all operations on foo in the same place!).But, if atomics exist, `do_thing` could do a relaxed store of its given foo address to a global, and another thread could then do a relaxed load of that and dereference that to get foo's value. And if the compiler decides to reorder the store to `foo` to after the fence (as a C99 compiler can), the program now has a data race that it otherwise would not.
(hell, a C99 compiler could even insert `int tmp = something; something = 123; something = tmp;` for any variable or global or field `int something`, in any place in the code (as long as `something` is known to be mutable), which'd break basically every multithreaded thing; this effectively one of the things the "Threads Cannot Be Implemented As a Library" paper mentions)
dokyun 10 hours ago | parent
You use the Plan 9 compiler to compile Plan 9 programs. Attempts at trying to get GCC to compile Plan 9 programs correctly have been mixed.
> So it effectively quietly makes a bunch of quiet guarantees that C99 doesn't
Yeah. It's not a C99 compiler.
> Even if not explicit, that's still semantics outside of those C99 specifies, that code may be relying on.
Yeah. They're not C99 programs.
> Fact of the matter is, you cannot make a full atomics library that will necessarily work correctly on C99.
Sounds like C99's problem.
aw1621107 2 days ago | parent
At least to me, section 4 seems to imply that the "assumption" is a bit more than just that. For example:
> We know of at least four optimizing compilers (three of them production compilers) that performed this transformation at some point during their lifetime; sometimes at least partially reversing the decision when the implications on multi-threaded code became known.
i.e., there are real-life instances of the lack of compiler/language support leading to miscompilations. Yes, that example is talking about threads, but I think a similar argument should apply to atomics.
> I gave you an example of atomics being handled by a library
The thing is that nothing in that man page indicated to me that that library wouldn't be subject to problems similar to that described in Boehm's paper; e.g., for all I know that library is the Plan 9 moral equivalent of pthreads and is subject to similar problems pthreads is when a non-thread-aware optimizing compiler is used.
> Kencc is a very "dumb" compiler that does little magic
This could (partially?) address things though. If Plan 9 always uses that compiler and if there's no interest in implementing the optimizations Boehm describes then that would obviate 2 of the 3 correctness issues Boehm describes (though that would also imply that Plan 9 uses its own dialect of C with its own set(s) of guarantees, both explicit and implied). The only thing I'm uncertain about is to what extent hardware reordering would be relevant; I thought I remember reading about that making things interesting, but I don't remember off the top of my head to what extent.
pjmlp 2 days ago | parent
To quote Rob Pike,
"Early 1990s: Alef (Phil Winterbottom) grafted the concurrency and communications model of Newsqueak onto a more traditional compiled C-like language.
Problem: with C's memory model in a concurrent world, hard to know when to free items.
All the other languages in this talk are garbage-collected, which is essential to easy concurrent programming."
http://go-lang.cat-v.org/talks/slides/emerging-languages-cam...
peterfirefly 2 days ago | parent
which is precisely why they can get away with a library seemingly without having to specify or coordinate anything with the compiler.
glouwbug 2 days ago | parent
jcranmer 2 days ago | parent
* thread_local and _Atomic support from C11
* fopen("foo", "x") from C11
* aligned_alloc from C11
* CMPLX macro from C11
* C++ attribute syntax ([[foo]] flows better to me than __attribute__((foo)) does)
* C23 finally gets rid of _Who _The _Hell _Thinks _Users _Like _This-style _Keywords.
* Multi-level break/continue in C2y
* Variable declarations in if conditions.
* Some of the new operations like ckd_add or stdc_rotate_left. I do dislike the names, but meh, at least C is starting to gain a competent set of fundamental operations.
Joker_vD 2 days ago | parent
Also, I'm personally still waiting for a "shift left" operator having cleaner wording in the standard:
If E1 has an unsigned type, the value of the result is E1 × 2**E2, wrapped
around. If E1 has a signed type and nonnegative value, and E1 × 2**E2 is
representable in the result type, then that is the resulting value; otherwise,
the behavior is undefined.
What does "otherwise" means here? It can't mean "if E1 has an unsigned type, or negative value, or E1 × 2**E2 is unrepresentable, then the behavior is undefined" because the part "if E1 has an unsigned type" has already been covered by the previous clause. Does it mean "If E1 has negative value, or E1 × 2**E2 is unrepresentable, then the behavior is undefined"? Because that's a ridiculous ordinance: (-1) << 1 is UB?!SAI_Peregrinus 2 days ago | parent
Joker_vD 2 days ago | parent
Shifting into the sign bit is also UB. Can I please have a language that actually has bitwise operators with defined semantics? No CPUs had differentiated between SAL/SHL for more than 30 years, if that was the original justification.
I'm fine with left shift trapping on overflow. I'm fine with it just wrapping around. I'm even fine with bitwise shifts simply unapplicable to ints ("cast to unsigned first, then cast back to int"). I am not fine with an operator that invokes UB for literally half of the values of the lhs's domains.
dcrazy 2 days ago | parent
> The << operator performs a smart shift, which defines a result for a shift of any value.
> - Using a negative value for rhs performs a right shift using abs(rhs).
> - Using a value for rhs that is greater than or equal to the bit width of lhs is an overshift, resulting in zero.
> - Using any other value for rhs performs a left shift on lhs by that amount.
And while the docs don’t say it, `Int.max << 1 == -2`. And in fact, `T.max << 1 == -2` for all `T: BinaryInteger`.
rerdavies 7 hours ago | parent
e.g.
MOV AX, 1
MOV CL, 33
SHL AX, CL
Afterward, on an 80186, AX contains 2, since the contents of CL are treated MOD 32. The AX register gets shifted by one bit. On an 8086, AX contains zero, since the 8086 implementation shifts by one bit 33 times. (Yes, it will shift 255 times, taking 1024 CPU cycles if you tell it to do that).Given a choice, I'd prefer performant bit-twiddling in exchange for UB in cases that never occur in practice.
Joker_vD 1 hour ago | parent
But variable shifts also happen sometimes, which brings me to the next point:
> in exchange for UB in cases that never occur in practice.
It does happen, and people depend on what their platform does. That's one of the things that sunk John Regehr's proposal for "Friendly C" [0]:
After publishing the Friendly C Proposal, I spent some time discussing its design with people,
and eventually I came to the depressing conclusion that there’s no way to get a group of C
experts — even if they are knowledgable, intelligent, and otherwise reasonable — to agree on
the Friendly C dialect. There are just too many variations, each with its own set of performance
tradeoffs, for consensus to be possible. [...] Another example is what should be done when a
32-bit integer is shifted by 32 places (this is undefined behavior in C and C++). Stephen Canon
pointed out on twitter that there are many programs typically compiled for ARM that would fail
if this produced something besides 0, and there are also many programs typically compiled for
x86 that would fail when this evaluates to something other than the original value. So what is
the friendly thing to do here?
So. You want a modulo behaviour? Write "expr << (var & 32)" or "expr << (var & 64)", the compiler will recognize this idiom and emit a simple "mov cl, var; shl eax/rax, cl" on x64 and RISC-V, and an additional explicit AND mask on ARMs. You want an overshift? Well, that's the default semantics, and it would emit an simple shift on ARM, and "xor eax, eax; shl edx/rdx, cl; cmp ecx, 32/64; cmovb eax/rax, edx/rdx" on x64 (and something similar on RISC-V). It's your choice!uecker 2 days ago | parent
But all this signed overflow UB can be turned into traps using sanitizers. In practice, generally mandating a trap is difficult though in ISO C. My plan is to mandate this as part of a special mode, in which case I would just leave this UB in the main standard.
(One problem we also have that some special interests groups in WG14 are rather hostile to our work and the rest of the C community - so I encourage everybody who is actually using C and caring about it to join).
Joker_vD 2 days ago | parent
At least make it "change of the sign bit is UB" — if 0 shifts into the sign-bit of a positive number, or 1 shifts into the sign-bit of a negative number, that shouldn't be UB. This would make left-shifts of negative numbers well-defined, their behaviour being that of two's complement numbers. And C23 already demands two's complement from the implementations, doesn't?
> signed overflow UB can be turned into traps using sanitizers.
Well, yeah, sure, but shifting -1 one place to the left doesn't overflow, it produces -2; only shifting INT_MIN or something up to and including INT_MIN + (INT_MAX/2) would overflow.
uecker 1 day ago | parent
uecker 1 day ago | parent
Joker_vD 1 day ago | parent
1) ........................ The behavior is undefined if the right operand is negative,
or greater than or equal to the width of the promoted left operand.
2) The value of E1 << E2 is the unique value congruent to E1 × 2**E2 modulo 2**N, where
N is the width of the type of the result.
[Note 1 : E1 is left-shifted E2 bit positions; vacated bits are zero-filled. — end note]
3) The value of E1 >> E2 is E1 / 2**E2, rounded down.
[Note 2 : E1 is right-shifted E2 bit positions. Right-shift on signed integral types is
an arithmetic right shift, which performs sign-extension. — end note]
So you can adopt that as well. In any case: since C23/C++23 the integers are two's complement, so make them behave so. Again, I don't mind signed overflow being undefined all that much (although again, I feel like there has to be some middle ground between "full-blown UB and nasal demons" and "the implementations/sanitiziers are allowed to trap it instead of silently wrapping/assuming it never happens") so perhaps you can slightly deviate from C++ here: it apparently allows 1 << (INT_WIDTH - 1) to become INT_MIN, fully defined-ly, maybe this can still be UB in C? But (-1) << (whatever up to INT_WIDTH - 1) absolutely should be defined, it's just ridiculous otherwise.uecker 5 hours ago | parent
leni536 2 days ago | parent
C++? It has defined semantics since C++20.
Joker_vD 1 day ago | parent
4) The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits
are filled with zeros. If E1 has an unsigned type, the value of the result
is E1 × 2**E2, wrapped around. If E1 has a signed type and nonnegative value,
and E1 × 2**E2 is representable in the result type, then that is the resulting
value; otherwise, the behavior is undefined.
5) The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an
unsigned type or if E1 has a signed type and a nonnegative value, the value of
the result is the integral part of the quotient of E1 / 2**E2. If E1 has a
signed type and a negative value, the resulting value is implementation-defined.
It's marginally more well-defined, sure, but not fully. The right shifts of negative numbers are no longer UB but are still an unportable mystery; and the left shifts of negative values are still outright UB.leni536 1 day ago | parent
https://eel.is/c++draft/expr.shift
I thought this was part of the guaranteed two's complement change in C++20.
leni536 1 day ago | parent
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/n49...
This is indeed from the C++20 paper P1236R1:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p12...
Joker_vD 1 day ago | parent
In any case, thanks; it's nice to see C++ at least fully embracing the mandate on integers being two's complement, finally. There is enough footguns already without basic arithmetic operators being UB half of the time.
pjmlp 2 days ago | parent
Operations are well defined, and to do stuff like in C, you must do Unchecked_Something calls.
rerdavies 7 hours ago | parent
jcranmer 2 days ago | parent
That's exactly what it means, see the change mentioned in https://www.open-std.org/jtc1/sc22/wg14/www/docs/n723.htm. (Previously, in C89, left shift on signed integers was completely undefined, only unsigned integers had a defined left-shift operation.) My guess is that it has to do with signed-magnitude representation causing left shifts to be absolute garbage with negative numbers. You also have to remember that the standard having undefined behavior doesn't preclude implementations from defining that behavior, and this is especially true with the freewheeling nature of a lot of the original undefined behavior introduced in C89 and C99.
> Also, C23 (and C2y) is moving ever closer to actually having defined semantics.
Eh, having seen how some of the sausage gets made, there's definitely been some cases where the overeager urge to rip out undefined behavior from the standard has worsened the quality.
uecker 2 days ago | parent
I also do not see where the quality suffered. There were some rather minor issues we created and need to cleaned up.
jcranmer 2 days ago | parent
This kind of attitude tends to make me suspect that you are disinclined to actually listen to my feedback.
uecker 2 days ago | parent
uecker 2 days ago | parent
(Also a counter proposal would be constructive.)
uecker 2 days ago | parent
pwdisswordfishq 2 days ago | parent
Very informative, thanks
uecker 1 day ago | parent
jcranmer 1 day ago | parent
jcranmer 2 days ago | parent
uecker 1 day ago | parent
Joker_vD 2 days ago | parent
First of all, why not make it implementation-defined? That seems rather more useful for the programmer. Second, I'm personally aware of only a single machine with signed-magnitude representation, IBM 7090, and its ALS (Accumulator Left Shift) instruction treated the sign bit as sticky; amusingly, LGL (Logical Shift Left) did as well, so you had to put the value being shifted into the MQ register, not the accumulator, when you wanted an unsigned shift. So... no, that can't be it?
jcranmer 2 days ago | parent
And one of those kinds of "blast radius" is traps, and I suspect the original motivation for making signed integer overflow undefined behavior was precisely to preserve the ability to trap on signed integer overflow, which couldn't be implementation-defined. But the records of the discussions leading up to C89 and C99 are just too fragmentary for me to be certain.
userbinator 2 days ago | parent
Joker_vD 2 days ago | parent
Wait. An implementation-defined behaviour isn't allowed to be trap/termination of execution? Okay, make it unspecified then, with the options of trapping or calculating some unspecified (not indeterminate) integer value.
> N3863
Hm?
Because implementation-defined behavior relies on the definition of unspecified behavior, it
means that implementation-defined behavior requires two or more possibilities to be defined in
the standard. However, a brief look at its uses in the standard clearly shows that there isn’t
always a list of potential behaviors to pick from.
Eh, I'd argue that omission of such a list is merely an implicit infinite ("two or more", you know) list of possibilities, its construction being self-evident. N3783 5.2.1.2p1.1: Physical source file multibyte characters are mapped, in an implementation-
defined manner, to the source character set (introducing new-line characters for end-of-line
indicators) if necessary.
There is an infinite list of possible ways to map multibyte characters to the source character set — an implementation has to pick one and document it. N3783 5.2.1.3p1: A conforming implementation shall produce at least one diagnostic message
(identified in an implementation-defined manner)
There is an infinite list of possible diagnostic messages... N3783 5.2.2.2p1-2: In a freestanding environment (in which C program execution can take
place without any benefit of an operating system), the name and type of the function called at
program startup are implementation-defined.
There is an infinite list of possible C identifiers that an implementation may chose as the name of the function to call at program startup.jcranmer 2 days ago | parent
> Wait. An implementation-defined behaviour isn't allowed to be trap/termination of execution? Okay, make it unspecified then, with the options of trapping or calculating some unspecified (not indeterminate) integer value.
Unspecified behavior also isn't allowed to be a trap. The difference between unspecified and implementation-defined is whether or not the compiler has to document its choice; the difference between unspecified and undefined is whether or not the choice is limited.
leni536 2 days ago | parent
https://open-std.org/JTC1/SC22/WG14/www/docs/n3220.pdf#subsu...
> Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
There is no reason that this wording strategy could not be reused for bit shifts.
In general there is no constraint that unspecified or implementation-defined behavior can't be a trap. Without further qualification "unspecified behavior" or "implementation-defined behavior" is still open-ended, and it's up to the surrounding standard text to restrict the behavior further.
https://open-std.org/JTC1/SC22/WG14/www/docs/n3220.pdf#subse...
> behavior, that results from the use of an unspecified value, or other behavior upon which this document provides two or more possibilities and imposes no further requirements on which is chosen in any instance
"Possibilities" include trap
derf_ 2 days ago | parent
No, it was implementation defined, which is very different. Every implementation you were ever likely to encounter defined it reasonably. Making some of it undefined now has created a bunch of code that uses macros to cast back and forth between unsigned to get the expected behavior (or, for cases where it is provable that the result is representable, to replace the left shift with a multiplication and hope the optimizing compiler translates it back, because compilers now warn on any left shift of a negative value, despite the fact that the semantics are supposed to be the same as the corresponding multiplication now).
In other words, it created a mess for basically no reason.
jcranmer 2 days ago | parent
> The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 multiplied by the quantity, 2 raised to the power E2, reduced modulo ULONG_MAX+l if E1 has type unsigned long, UINT_MAX+1 otherwise. (The constants ULONG_MAX and UINT_MAX are defined in the header <limits.h>.)
You notice what's not mentioned there? What happens if E1 is signed. The standard says nothing. And when the standard says nothing, it's undefined behavior.
MobiusHorizons 1 day ago | parent
derf_ 1 day ago | parent
Some operators (the unary operator ~ , and the binary operators << ,
>> , & , ^ , and | , collectively described as bitwise operators)
shall have operands that have integral type. These operators return
values that depend on the internal representations of integers, and
thus have implementation-defined aspects for signed types.afiori 1 day ago | parent
MobiusHorizons 1 day ago | parent
I guess I can understand that signed shifts bump up against how the hardware represents signed numbers at the bit level, but it’s not like the assembly is unclear on any given platform.
Joker_vD 1 day ago | parent
But if it is true, then yeah, that'd make (some) sense. I say "some" because surely in this case the implementations could simply emit the "logical shift left" instruction for the shift operation both for ints and unsigned ints? I don't think there ever was a machine that actually tracked the signedness/unsignedness of registers.
MobiusHorizons 1 day ago | parent
mitxela 2 days ago | parent
monocasa 2 days ago | parent
I always took those to mean "we're sure a bunch of you are already using the keyword we wanted to use as typedef in your codebase, so here's something that's in the standard's namespace, and you can typedef it to something sane for your codebase".
uecker 2 days ago | parent
C23 broke a lot of code by making this proper lower case keywords, we probably should have waited longer.
pjmlp 2 days ago | parent
Bounded checked slices like proposed by Dennis Ritchie, or Walter Bright, remains a pipe dream.
NooneAtAll3 2 days ago | parent
_Generic isn't generics/templates
it is function overload
foldr 2 days ago | parent
WalterBright 2 days ago | parent
(D sez `char` is unsigned. No more nonsense!)
C also needs to implement proper arrays. D does it, and that one feature is worth its weight in gold:
https://www.digitalmars.com/articles/C-biggest-mistake.html
It's sooo nice to not have array overflows anymore.
avadodin 2 days ago | parent
Not that I disagree with making basic types unsigned by default, just not in C.
pjmlp 2 days ago | parent
JdeBP 2 days ago | parent
WalterBright 1 day ago | parent
I bet a lot of existing C code will break if one sets char to signed with a compiler switch.
Anybody reviewing C code and sees just char will have to waste time wondering if the code handles the implementation-defined signed-ness correctly.
And yes, I know about stdint.h, a poor attempt to deal with this problem.
dzaima 1 day ago | parent
WalterBright 1 day ago | parent
WalterBright 1 day ago | parent
char - unsigned 8 bits
byte - signed 8 bits
ubyte - unsigned 8 bits
char is used for utf-8, byte and ubyte are signed and unsigned small integers. This seems like small thing, but it makes D code much more readable than C.fsckboy 8 hours ago | parent
i'm old school. ascii is 7 bits, if you're using the 8th bit you probably want a sign since you've left the realm of ascii chars
hollowonepl 2 days ago | parent
uecker 2 days ago | parent
pjmlp 2 days ago | parent
hollowonepl 2 days ago | parent
I'm not critical about languages evolving.. just the tech changes so quickly that perhaps standardising it doesn't make any sense any more.
I wonder if languages like RUST or GO will ever become ISO standards.. I do believe all these new platforms won't even get close to that need.
BigTTYGothGF 2 days ago | parent
Spoken like someone who's never felt the frustration of having a grounded plug and the only available sockets being two-prong.
yuye 1 day ago | parent
jcranmer 2 days ago | parent
> I wonder if languages like RUST or GO will ever become ISO standards..
I doubt it, but mostly because everyone I know on the ISO standardization committees have expressed frustration with ISO (the organization) in ways that I haven't seen for other standards bodies like Ecma.
pjmlp 2 days ago | parent
Haven't travelled much around Europe in the last decades, or had to deal with electricity projects, I gather.
Nowadays, due to the ways of ISO most languages end up going with ECMA, as ISO processes are considered somehow Jurassic.
However a resurgence in cold war like state of the world might indeed, bring us back to ISO as main driver for standardising languages instead of ECMA or open source, due to the way it ensures specifications and implementations aren't tainted by foreign politics.
childintime 1 day ago | parent
pjmlp 2 days ago | parent
In what concerns C itself, as the language authors, they did not care that much about the standard, hence why it is mostly C89 with Rob Pike ideas on how header files should be organised, and other Plan 9 specific idioms.
sylware 2 days ago | parent
I don't really mind though as it seems the far away real future is something like everything RISC-V assembly, "C" would only be the binary specification.
And some python/javascript/etc interpreters written in RISC-V assembly themselves, because sometimes my code can be slow and non-rigorous.
iberator 2 days ago | parent
What software do you write?
dzaima 2 days ago | parent
Well, considering that gcc doesn't support the FENV_ACCESS pragma, and as such all fesetround calls are always UB, it is valid for it to act as if the rounding mode never changes from the default :)
uecker 2 days ago | parent
amszmidt 2 days ago | parent
Someone 2 days ago | parent
“ISO C language standard requires that C compilers support the following three pragmas, and some C++ compiler vendors support them, to varying degrees, in their C++ frontends:
#pragma STDC FENV_ACCESS arg (1)
#pragma STDC FP_CONTRACT arg (2)
#pragma STDC CX_LIMITED_RANGE arg (3)
where arg is either ON, OFF, or DEFAULT.”⇒ it seems C compilers must support this (but C++ compilers need not)
gpderetta 2 days ago | parent
At least some GCC devs claimed that the standard does not impose any requirements on these macros if a compiler does not claim strict ISO/IEC 6055 (aka IEE 754) conformance (which is still optional, and gcc and clang do not claim it).
Other devs disagree.
Bratmon 1 day ago | parent
But it does kind of support the whole "GCC C is not standard C" point.
dzaima 1 day ago | parent
Joker_vD 2 days ago | parent
4 Conformance
A strictly conforming program shall use only those features of the language and
library specified in this document.
[...]
The two forms of conforming implementation are hosted and freestanding. A
conforming hosted implementation shall accept any strictly conforming program.
And it doesn't say anywhere in the standard that it's optional to support FENV_ACCESS pragma — it's a feature in the standard, so if you want to be a conforming implementation, you have to implement it. Of course, you can always be a non-conforming implementation, that also is allowed. Who's to stop you?dzaima 2 days ago | parent
That said, I was surprised that it's mandatory by the standard (and wasn't made optional later like VLAs); feels rather unlike C to require such precise control over something it doesn't even specify any behavior of - imagine if we had mandatory macros for integer overflow behavior and whatnot!
...That "that said" said, C11 does also contain:
> Each of the macros FE_DOWNWARD FE_TONEAREST FE_TOWARDZERO FE_UPWARD is defined if and only if the implementation supports getting and setting the represented rounding direction by means of the fegetround and fesetround functions.
so presumably one could just not define those and get away with not implementing (at least the rounding mode part of) fenv.h, properly allowing the `#define FLT_ROUNDS 1` hack? gcc+glibc does define those, though.
Syzygies 2 days ago | parent
omoikane 2 days ago | parent
/*
* the behavior of the #pragma directive is implementation defined.
* this implementation defines it as follows.
*/
do_pragma ()
{
close (0);
if (open ("/dev/tty", O_RDONLY) != 0)
goto nope;
close (1);
if (open ("/dev/tty", O_WRONLY) != 1)
goto nope;
execl ("/usr/games/hack", "#pragma", 0);
execl ("/usr/games/rogue", "#pragma", 0);
execl ("/usr/new/emacs", "-f", "hanoi", "9", "-kill", 0);
execl ("/usr/local/emacs", "-f", "hanoi", "9", "-kill", 0);
nope:
fatal ("You are in a maze of twisty compiler features, all different");
}
It's gone by gcc 1.21: #if 0
/* This was a fun hack, but #pragma seems to start to be useful.
By failing to recognize it, we pass it through unchanged to cc1. */
...
#endifim3w1l 2 days ago | parent
mitxela 2 days ago | parent
im3w1l 2 days ago | parent
sethev 2 days ago | parent
AnimalMuppet 2 days ago | parent
busfahrer 1 day ago | parent
"You are in a maze of twisty compiler features, all different" is a riff on the game Colossal Cave Adventure ("you are in a maze of twisty little passages, all alike")
fooker 1 day ago | parent
Whether a compiler implements the standard is up to the compiler developers and maybe its users.
In an era where code portability was of utmost importance, maybe this made a lot of sense.
But now we can pretty much treat a compiler and its libraries the standard for the language, like Rust does.