Live data from Hacker News

A convenient untruth: Array notation in C is a lie

blog.feabhas.com

161–170 of 176 posts

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

#161
post #158

Earlier quoted context omitted.

Unfortunately this is differently wrong in C# (and many other modern languages). arr here is not an array at all, but is actually a reference to an array and has different semantics from the the corresponding C declaration. This is not just being pedantic: using the same syntax for values and references does lead to confusion. At least C# has actual value types and 'ref' (but not value type arrays AFAIK), in Java thi…

I wouldn't agree to call it "wrong", since we're talking about languages that are trying to discourage you from doing your own memory management *(C#, that is, but I'd say this applies to any language running in a VM with gc, really). Thus, in such languages, there shouldn't be a conceptual or syntactical difference between values and references. The fact that C# allows to differentiate them from each other is a leak…

Having first class references has very little to do with manual memory management. As you said, confusing values with references is a very leaky abstraction, especially when you have mutable data.

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

#162
post #152

Earlier quoted context omitted.

> None of those 'lies' have anything to do with assembler. They do. Lets take a look at the very first one. "Array name is just a pointer". Assebler "array" is a name of a label. So its just named address in memory. Or we can alternatively say, that assembler array is constant pointer. `sizeof' returns size of array in bytes? Hmm... maybe that because main C abstraction for memory is the assembler one: memory is a co…

From an assembler point of view a structure of N elements of type T and an array of T[N] have exactly the same layout and are accessed in exactly the same way [1], but in C have wildly different semantics. Sizeof behaves exactly the same way for structs and arrays, so it is one of the few things in C that treat arrays "correctly". [1] although usually the offset is constant for a struct field access.

> but in C have wildly different semantics.

Can you show it with example? I'm not sure what you mean exactly.

Look:

    struct {
        int a, b, c;
    } foo;
    int *pfoo = (int*)&foo;
    printf("%d, %d, %d\n", pfoo[0], pfoo[1], pfoo[2]);
Code like this can have problems due to alignment, but at my opinion its not "wildly different semantics".

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

#163
post #162

Earlier quoted context omitted.

From an assembler point of view a structure of N elements of type T and an array of T[N] have exactly the same layout and are accessed in exactly the same way [1], but in C have wildly different semantics. Sizeof behaves exactly the same way for structs and arrays, so it is one of the few things in C that treat arrays "correctly". [1] although usually the offset is constant for a struct field access.

> but in C have wildly different semantics. Can you show it with example? I'm not sure what you mean exactly. Look: struct { int a, b, c; } foo; int *pfoo = (int*)&foo; printf("%d, %d, %d\n", pfoo[0], pfoo[1], pfoo[2]); Code like this can have problems due to alignment, but at my opinion its not "wildly different semantics".

In addition to being UB, the example doesn't illustrate the issue: arrays in C are not first class as they can't be passed by value and can't be assigned. The decay-to-pointer thing that prevent this regularity has nothing to do with asm.

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

#164
post #162

Earlier quoted context omitted.

> but in C have wildly different semantics. Can you show it with example? I'm not sure what you mean exactly. Look: struct { int a, b, c; } foo; int *pfoo = (int*)&foo; printf("%d, %d, %d\n", pfoo[0], pfoo[1], pfoo[2]); Code like this can have problems due to alignment, but at my opinion its not "wildly different semantics".

In addition to being UB, the example doesn't illustrate the issue: arrays in C are not first class as they can't be passed by value and can't be assigned. The decay-to-pointer thing that prevent this regularity has nothing to do with asm.

> 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 runtime costs. C also goes this way. So its inevitable for my code to work. With some precautions, but it would work everywhere.

> the example doesn't illustrate the issue...

Yes, I suggested it, and I asked you for some illustrative example, because I can't understand your reasoning from "arrays are not first class" to "nothing to do with asm". I see it other way: "arrays are not first class" is "asm mode".

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

#165
post #164

Earlier quoted context omitted.

In addition to being UB, the example doesn't illustrate the issue: arrays in C are not first class as they can't be passed by value and can't be assigned. The decay-to-pointer thing that prevent this regularity has nothing to do with asm.

> 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, but C array are not copyable and assignable and implicitly convert to pointers without any good reason. This has nothing to do with assembler whatsoever.

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

#166
post #158

Earlier quoted context omitted.

Unfortunately this is differently wrong in C# (and many other modern languages). arr here is not an array at all, but is actually a reference to an array and has different semantics from the the corresponding C declaration. This is not just being pedantic: using the same syntax for values and references does lead to confusion. At least C# has actual value types and 'ref' (but not value type arrays AFAIK), in Java thi…

I wouldn't agree to call it "wrong", since we're talking about languages that are trying to discourage you from doing your own memory management *(C#, that is, but I'd say this applies to any language running in a VM with gc, really). Thus, in such languages, there shouldn't be a conceptual or syntactical difference between values and references. The fact that C# allows to differentiate them from each other is a leak…

> Thus, in such languages, there shouldn't be a conceptual or syntactical difference between values and references. The fact that C# allows to differentiate them from each other is a leaky abstraction imho, built in to satifsy people form a C/C++ background.

It's not a leaky abstraction, it's a very specific design choice for performance reasons. Java also has this dichotomy between types, and for the same reason, although I believe they only delineate between primitive/non-primitive.

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

#167

Earlier quoted context omitted.

Right, in a language with dependent types there is a type-level difference between int[5] and int[7], but c is not such a language, therefore using a syntax that encourages the mistaken notion that there is a type-level difference between int[5] and int[7] would be misleading.

Saying int[5] and int[7] are the same type because the compiler doesn't enforce the difference is like saying JavaScript is untyped because there is no compiler to enforce types at all. Regardless of what the spec or the compiler says, you the programmer absolutely need to treat them separately. Especially in a language like C, where arrays aren't even self-describing at runtime.

no it isn't. javascript has types; they exist only at runtime, but they're there. it's patently false to say it's untyped. but type differences codify certain classes of differences, and it really isn't that crazy or unusual to say that the length of a vector, array, list, whatever you want to call it, isn't a difference of type. to say that there's a type difference even if the type checker disagrees just means you and the type checker are using different type systems.

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

#168
post #150

Earlier quoted context omitted.

Right, in a language with dependent types there is a type-level difference between int[5] and int[7], but c is not such a language, therefore using a syntax that encourages the mistaken notion that there is a type-level difference between int[5] and int[7] would be misleading.

A language doesn't need dependent types to make fixed-size arrays of differing size count as incompatible types. Rust does this, for example: // error: expected an array with a fixed size of 5 // elements, found one with 7 elements let x: [i32; 5] = [1,2,3,4,5,6,7];

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 the type, and it can be checked like above.

ghc has a limit on tuple sizes, and haskell makes no type distinction between [a] based on the number of elements in it, and it isn't dependently typed.

but again i don't know rust.

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

#169
post #150

Earlier quoted context omitted.

A language doesn't need dependent types to make fixed-size arrays of differing size count as incompatible types. Rust does this, for example: // error: expected an array with a fixed size of 5 // elements, found one with 7 elements let x: [i32; 5] = [1,2,3,4,5,6,7];

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.

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

#170
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…

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?
Post reply on HN