Live data from Hacker News

Mildly interesting quirks of C

gist.github.com

81–90 of 91 posts

Re: Mildly interesting quirks of C

#81
post #59

Regarding 12, alignment of bitfields, how I believe it works is that when the bitfield of type long is laid out, then the structure so far is considered to be a vector of storage cells whose size and alignment are those of long: struct foo { char a; long b: 16; char c; }; So, a has been laid into the structure, so the current offset is 1 byte. This is considered to be occupying a portion of an existing long type bitf…

The cell size is not necessarily the same as "long" - it can be whatever the compiler wants, so long as alignment of non-bitfield fields is appropriate. It doesn't even have to be the same for every bitfield.

If the bitfield is declared as long, then based on that specific cell size the decision is made whether to pack the bits into the current cell or a new cell.

If a leading char member is followed by a uint64_t bitfield that is 57 bits wide, a new cell will be allocated for those 57 bits at offset 8. The char is considered to be a field of 8 bits allocated in an existing 64 bit cell, leaving 56. 57 cannot fit, and so the offset is bumped to the next cell alignment.

This is testable.

I'm only writing about GCC, not about ISO C, which specifies very little, allowing implementations latitude in choosing the underlying storage unit size and alignment for bitfields regardless of their declared type.

Re: Mildly interesting quirks of C

#82
post #79

> Typedef goes anywhere WTF. Here have some keyword soup, the order doesn't matter at all. Must be fun to write a C compiler.

This one is actually pretty simple, it works a lot like static or typedef. It's really just a modifier for what is being declared - in a typedef we're not declaring a name to refer to an instance (variable) of a type, but we're declaring a name to refer to the type itself.

Re: Mildly interesting quirks of C

#83
> Main directly calls this_is_not_directly_called_by_main in this implementation. This happens because: [...] LLVM assumes that bar() will have executed by the time main() runs.

I think this reasoning is slightly incorrect, although I don't blame the author as this is a very common misconception. I believe the correct reasoning might be as follows:

1. The compiler sees the pointer is dereferenced.

2. The compiler infers the pointer was not NULL.

3. The compiler determines a set of candidates for its target (which may be the universal set).

4. If it finds only one candidate, it just substitutes the target.

The critical thing to notice here is that the compiler doesn't need to care about the reachability of that candidate. It's making a conservative over-approximation, after all. You can witness the effect of this by formulating an impossible condition inside bar() that the compiler completely ignores: see [1]. Note the pointer assignment cannot have been implied by "bar() will have executed", as the execution of bar() could never lead to that assignment anyway!

[1] https://godbolt.org/z/W19javzqW

Re: Mildly interesting quirks of C

#84

Earlier quoted context omitted.

> all the undefined behaviour added for high performance computing UBs were added for cross-incompatibilities, where operations were too "core" (and / or untestable) for IBs to be acceptable. The reason was not performance (aside from not imposing a runtime check where that would have been possible) but portability: > 3.4.3 undefined behavior behavior, upon use of a nonportable or erroneous program construct or of er…

The standard also has implementation-defined behavior, doesn't it?

Their existence is mentioned in my comment so I am not sure what you are trying to say.

Re: Mildly interesting quirks of C

#85

> UB is impossible What? UB is clearly undesirable , but assuming it is impossible and deducing other outcomes must be meant are clearly wrong assumptions by the compiler writer. More sensible compilers (including older version of clang) do the right thing (TM) here and yield a compiler error. There were earlier attempts at do-what-i-mean programming languages. They are rightfully buried in history.

From where I stand many of do-what-i-mean programming languages are doing just great in distributed computing, Web and mobile OSes, taking over roles that used to be done in C and C++ during the last century.

Re: Mildly interesting quirks of C

#86
post #25

As someone who's moved on to Rust, I see this as one long list of nightmares.

I don't think the "I use Rust btw" comments contribute much to the discussion. C and Rust don't perfectly overlap, especially since Rust is more a replacement to C++ than C.

Most people that still cling to C instead of C++, do it because they are stuck in UNIX clones kernel stuff, embedded, or are religiously against anything else.

So whatever language Rust "replaces" is a kind of moot point, and then there is the whole ongoing integration with Linux, a UNIX clone kernel.

Re: Mildly interesting quirks of C

#87
post #20

Here are two of my favorite obscure quirks of C: struct X { char x[8]; }; struct X awoo(void); printf("%s\n", awoo().x); The above is UB in = C11. [0] struct X { char b[8]; } foo(); int *b = foo().b; printf("%s\n", b); The above is UB in >= C11 and valid in [0] https://wiki.sei.cmu.edu/confluence/plugins/servlet/mobile?c... [1] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1285.htm

I really wish both would be valid in C11. Or rather I wish I had "systems-C" where all the undefined behaviour added for high performance computing was filed off and defined as "whatever the platform does".

I think volatile variables are a good example of how we really don't want to rely on platform semantics most the time. We use volatile variables to tell the compiler "hey, the platform actually cares about these writes, so do them the way I wrote them."* But this is relatively rare! The vast majority of the time, we want the compiler to go nuts with constant propagation and reordering and all the other good stuff. A language where volatile was the default and "go nuts" was an explicit keyword would be really annoying to use.

* There are more things in heaven and earth (MMIO!) than are dreamt of in your memory model.

Re: Mildly interesting quirks of C

#90
post #63
post #6

Reminds me a bit of "Who Says C is Simple?" written by the people who wrote a C parser & analyser in OCaml (CIL): https://cil-project.github.io/cil/doc/html/cil/cil016.html Also: https://cil-project.github.io/cil/doc/html/cil/cil012.html

> return ({goto L; 0;}) && ({L: 5;}); What in the world…!! It’s in the GCC section so I assume it’s some kind of lambda-function-like compiler extension? That allows jumping between bodies of two different functions…!

It's "statement exprs": "A compound statement enclosed in parentheses may appear as an expression in GNU C. This allows you to use loops, switches, and local variables within an expression. [...] The last thing in the compound statement should be an expression followed by a semicolon; the value of this subexpression serves as the value of the entire construct." [0]

[0] https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html

Post reply on HN