Live data from Hacker News

A convenient untruth: Array notation in C is a lie

blog.feabhas.com

171–176 of 176 posts

Re: A convenient untruth: Array notation in C is a lie

#171
post #164

Earlier quoted context omitted.

> In addition to being UB, Yes, its UB. But I'm not persuade you to use this UB in real code: in real C code use offsetof from stddef.h. The only thing I want to say is: this code would work everywhere (if you pay attention to alignment). And its not coincidence by some chance: C mimics asm, because C needs to be 100% predictable to coder. Because asm use simpliest and the most obvious abstractions, with predictable…

> this code would work everywhere it does not, it will be miscompiled by modern compilers. > I asked you for some illustrative example, foo(T x) { x[0] = 1; } T x = {0}; foo(x); assert(x[0] == 0); The assertion fails for T = char[1], but succeed for T=std::array ; You could construct a similar example in pure C. std::array and C arrays compile down to the exact same code for access, have the exact same layout, etc, b…

>> this code would work everywhere

> it does not, it will be miscompiled by modern compilers.

Sorry, due to formatting bug I overlooked this.

Can you show me example of such a modern compiler? I suspect that you mean some C++ compiler, and they probably do, they would `miscompile' my example, because they treat struct in a matter similar to a class with vtable and all other stuff. But we are speaking about C, not C++. But if I'm mistaken with my suggestions, I'd like to know about modern compiler of C which prove me wrong. Such a proof can help me to understand modern C much better.

Re: A convenient untruth: Array notation in C is a lie

#172
post #169

Earlier quoted context omitted.

i don't know rust, but i read a bit about fixed size arrays in it and the fact that 32 is the largest fixed size array makes me suspicious that it works like a pair. you can do this without depending on a value, because the length is encoded in the type. like, you can have a type (a, b), and b can, of course, be of type (a, b), b again having type (a, b), and so on. then you always carry around the length encoded in…

> i read a bit about fixed size arrays in it and the fact > that 32 is the largest fixed size array This is exceptionally mistaken, where did you read it? Arrays in Rust top out at the maximum value of a platform-sized pointer, which is either 2^32 or 2^64 depending on platform.

"Arrays of sizes from 0 to 32 (inclusive) implement the following traits if the element type allows it:

Clone (only if T: Copy) Debug IntoIterator (implemented for &[T; N] and &mut [T; N]) PartialEq, PartialOrd, Eq, Ord Hash AsRef, AsMut Borrow, BorrowMut Default This limitation on the size N exists because Rust does not yet support code that is generic over the size of an array type. [Foo; 3] and [Bar; 3] are instances of same generic type [T; 3], but [Foo; 3] and [Foo; 5] are entirely different types. As a stopgap, trait implementations are statically generated up to size 32."

from the rust docs. https://doc.rust-lang.org/std/primitive.array.html

i sort of leapt to the conclusion that these traits couldn't be implemented because generically because the type system requires them to be implemented for each size of N, and they provide the first 32 as a nicety. the similarity to the situation with tuples in haskell, (http://stackoverflow.com/questions/2978389/haskell-tuple-siz...), and the fact that rust doesn't have dependent types and so a type couldn't depend on a value, and i just kind of guessed at a possible reason.

Re: A convenient untruth: Array notation in C is a lie

#173
post #169

Earlier quoted context omitted.

> i read a bit about fixed size arrays in it and the fact > that 32 is the largest fixed size array This is exceptionally mistaken, where did you read it? Arrays in Rust top out at the maximum value of a platform-sized pointer, which is either 2^32 or 2^64 depending on platform.

"Arrays of sizes from 0 to 32 (inclusive) implement the following traits if the element type allows it: Clone (only if T: Copy) Debug IntoIterator (implemented for &[T; N] and &mut [T; N]) PartialEq, PartialOrd, Eq, Ord Hash AsRef, AsMut Borrow, BorrowMut Default This limitation on the size N exists because Rust does not yet support code that is generic over the size of an array type. [Foo; 3] and [Bar; 3] are instan…

Ah I see, yes the explanation there is correct. The types exist up to ginormous sizes, but the standard library only implements certain convenience traits for certain sizes (though using newtypes, you can implement those traits yourself for any array size you want). The specific feature we're lacking is type-level numerals, which is on the way towards but not anywhere close to a dependent type system AIUI.

Re: A convenient untruth: Array notation in C is a lie

#174
post #171

Earlier quoted context omitted.

> this code would work everywhere it does not, it will be miscompiled by modern compilers. > I asked you for some illustrative example, foo(T x) { x[0] = 1; } T x = {0}; foo(x); assert(x[0] == 0); The assertion fails for T = char[1], but succeed for T=std::array ; You could construct a similar example in pure C. std::array and C arrays compile down to the exact same code for access, have the exact same layout, etc, b…

>> this code would work everywhere > it does not, it will be miscompiled by modern compilers. Sorry, due to formatting bug I overlooked this. Can you show me example of such a modern compiler? I suspect that you mean some C++ compiler, and they probably do, they would `miscompile' my example, because they treat struct in a matter similar to a class with vtable and all other stuff. But we are speaking about C, not C++…

It is hard for compilers to miscompile this specific example as it doesn't do much at all.

The idea is that a write to pfoo[1] couldn't possibly alias with any write to foo, so the compiler should be free to reorder accesses if profitable. This is the same in C and C++ and has nothing go do with vtables.

For what is worth, I couldn't get gcc, clang and icc it to miscompile [¹] a slightly changed example, so either it is not actually UB or compilers still refrain to make this kind of optimization as it would break way too much code.

[¹] i.e. they elect to reload from the struct after writing to the array and vice versa even when it would be profitable not to do so.

Re: A convenient untruth: Array notation in C is a lie

#175
post #170

Earlier quoted context omitted.

> this code would work everywhere it does not, it will be miscompiled by modern compilers. > I asked you for some illustrative example, foo(T x) { x[0] = 1; } T x = {0}; foo(x); assert(x[0] == 0); The assertion fails for T = char[1], but succeed for T=std::array ; You could construct a similar example in pure C. std::array and C arrays compile down to the exact same code for access, have the exact same layout, etc, b…

Okey... Now I cant understand only one thing: how do you jump in conclusions to your last sentence? If you use asm and try to pass array into function, then you will pass address of array, not a copy of array on stack. Looks similar to C behaviour, isn't it?

Whether you copy or pass by reference has everything to do with the language semantcis, ABI and calling convention and nothing to do with asm.

For example, if you look at the generated asm, C on amd64 will happily pass a struct by copy in registers, but will pass an array by address.

The designers of C decided to give arrays pass by reference semantics and struct pass by value [1]; this was done because is convenient: you often want to iterate through arrays and pointers are the most generic way, but it does make arrays not first class.

[1] admittedly traditional C couldn't pass structs at all.

Re: A convenient untruth: Array notation in C is a lie

#176
post #88
post #42

E.g. Clickbait.

We detached this subthread from https://news.ycombinator.com/item?id=13236850 and marked it off-topic.

What part of "Array notation in C is a lie".... (End of article "p.s. It isn't") isn't an exact example of clickbait?
Post reply on HN