Live data from Hacker News

A convenient untruth: Array notation in C is a lie

blog.feabhas.com

141–150 of 176 posts

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

#141
post #39

This gets weird when you compare it to how structs work in C. Both are complex data types, so I sometimes forget that array semantics are totally different to struct/union semantics. Unlike arrays, in ANSI C, structs are real value types. You can pass them by-value to functions, return them by-value from functions and assign them by-value to variables of the same type. Also, structs never work like pointers to themse…

Yes, and there's another inconsistency here as well. Arrays and pointers into arrays are accessed with the same syntax:

  char a[3], *pa = a;
  ... a[0] ... pa[0] ...
In contrast, with a struct, you have to know which one you have and use the correct operator:

  struct { int x; } s, *ps = &s;
  ... s.x ... ps->x ...
To be consistent, they could either have made '.' work in both cases, or forced you to write things like '(*pa)[0]'.

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

#142
C has no more real arrays than assembly: for the cpu, it's just differently indexed pointers in the end. But arrays in C have some notable differences from pointers in C however, please read a good detailed description here: http://eli.thegreenplace.net/2009/10/21/are-pointers-and-arr...

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

#143

Brings back fond memories of the first time I learned C, when I really had to dig into what the difference was between storage durations (auto/stack, dynamic/heap, static, thread local). It makes it increasingly important to think about when an object is going to be stored, and for how long. To me this is still a really useful concept that most high-level languages seem to have all disregarded in favor of extremely e…

most high-level languages seem to have all disregarded in favor of extremely eager GC or refcounting, with the exception of Rust. Because Rust does a good job of hiding the fact it isn't a high level language. Sure you have ML type checking, pointer safety rules, multiple returns. But if you can see past the syntax sugar you realize it is just C (with guardrails).

High level abstraction or syntax sugar?

Terrorist or freedom fighter?

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

#144

Earlier quoted context omitted.

I don't agree with: int[5] arr; being a more readable syntax. The problem is that if you later have: int[7] arr2; It would make sense that arr and arr2 are different types (as the thing on the left is different) while they are the same type and you should be able (hopefully!) to use them as arguments to the same function. On the other hand I agree that: int* arr; makes more sense than more commonly used: int *arr; al…

Well, had C been really strongly-typed then the hypothetical 'int[5]' and 'int[7]' would have been different types, and 'int[]' would be the size-agnostic type for an array. Then you could easily get creature comforts such as: typedef uint8_t[16] guid_t; typedef uint8_t[20] sha1_digest_t; These typedefs would then have copy assignment, pass-by-value semantics and everything else you'd expect from value types.

> Then you could easily get creature comforts such as:

    > typedef uint8_t[16] guid_t;
    > typedef uint8_t[20] sha1_digest_t;
> These typedefs would then have copy assignment, pass-by-value semantics and everything else you'd expect from value types.

What does this accomplish that a struct can't?

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

#145

Earlier quoted context omitted.

The point of the article is that sometimes they're not equivalent, and that creates a lot of confusion. Please read it before commenting on it.

I thought the same things as GP. Anyone who has read K&R (as any C programmer ought to have) will know everything in this article.

I'd go so far as to say that any C programmer at all (whether or not they've read K&R) will know everything in this article. However, it's probably interesting or useful for people who are either in the process of learning C, or people who have to read and write it occasionally, but never truly learned the language. These are definitely all pain points for people coming to C from higher-level languages.

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

#146

C now has static array indices. For example void func(int arr[static 8]) {} imposes a limit on the size of the array that can be passed as an argument (you cannot pass an array of 7 or fewer elements.) I'd suggest that to the author, but given the article, I fear it may give him a heart attack.

I know they're called static array indices, and they're called that because of the use of the keyword `static`, but they don't have to be compile time constant at all (and checks aren't performed at compile time, IIRC). You can have the following: void foo(size_t len, int arr[static len]); Which is really useful in asserting that you won't pass in the null pointer at runtime (so you can remove any `if (arr) {}` check…

> Taken further, this exact method is what makes the restrict keyword usable in practice. One of the main problems of the restrict keyword is that you shouldn't be aliasing pointers. [..] one of the biggest problems of aliasing: passing in two null pointers

You shouldn't be dereferencing NULL pointers. Aliasing them is perfectly fine.

The non-aliasing requirements of restrict kick in only when you are actually accessing (and modifying!) the object referenced by an lvalue based on an expression of the restrict qualified pointer. So NULL pointers don't matter because first, they do not point to an object, and second, if you dereference them, you're already in UB land anyway. Correct code will not dereference NULL pointers, therefore the restrict qualification means absolutely nothing on code that opts to not try access anything through NULL pointers.

EDIT:

N1256 6.7.3.1p4 under Formal definition of restrict (emphasis mine):

> During each execution of B, let L be any lvalue that has &L based on P. If L is used to access the value of the object X that it designates, and X is also modified (by any means), then the following requirements apply: T shall not be const-qualified. Every other lvalue used to access the value of X shall also have its address based on P. Every access that modifies X shall be considered also to modify P, for the purposes of this subclause. If P is assigned the value of a pointer expression E that is based on another restricted pointer object P2, associated with block B2, then either the execution of B2 shall begin before the execution of B, or the execution of B2 shall end prior to the assignment. If these requirements are not met, then the behavior is undefined.

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

#147

Earlier quoted context omitted.

Surely it would still mirror use if the operators were applied to the type, rather than the identifier? Preferring 'char ٭arr[X]' over '٭char[X] arr' seems arbitrary to me. I see no reason the 'declaration mirror use' principle can differentiate between the two. Personally I prefer the latter since it makes it easy to separate the type from the identifier.

> Surely it would still mirror use if the operators were applied to the type, rather than the identifier? The "type" is actually a list of storage class specifiers (static, extern, auto, register, typedef, _Thread_local), type qualifiers (const, volatile, restrict) and type specifiers (int, char, float, double, signed, unsigned, long, short, void). Imagine the soup of keywords that would have to be at the deepest lev…

static, const, and volatile modify the variable itself, not what values it's legal for it to contain. So the outcome should be:

    static const volatile *unsigned char[X] arr;
Or, because signedness should be a property of the type, not an arbitrary modifier:

    static const volatile *char[X] arr;

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

#148

Earlier quoted context omitted.

I don't agree with: int[5] arr; being a more readable syntax. The problem is that if you later have: int[7] arr2; It would make sense that arr and arr2 are different types (as the thing on the left is different) while they are the same type and you should be able (hopefully!) to use them as arguments to the same function. On the other hand I agree that: int* arr; makes more sense than more commonly used: int *arr; al…

I mean, int[5] and int[7] would be different types, and lots of bugs happen from passing something besides the appropriate array type to a function. That being said, most languages which disambiguate between int[5] and int[7] provide some kind of polymorphism (and usually store it as a struct of size + data, to enable that). For example: you can define a first function that goes from t[N]->t pretty easily, and it wou…

Are there languages where these would be different types? Could they still be passed to a function that accepts a variable length array?

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

#149

Earlier quoted context omitted.

I mean, int[5] and int[7] would be different types, and lots of bugs happen from passing something besides the appropriate array type to a function. That being said, most languages which disambiguate between int[5] and int[7] provide some kind of polymorphism (and usually store it as a struct of size + data, to enable that). For example: you can define a first function that goes from t[N]->t pretty easily, and it wou…

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.

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

#150

Earlier quoted context omitted.

I mean, int[5] and int[7] would be different types, and lots of bugs happen from passing something besides the appropriate array type to a function. That being said, most languages which disambiguate between int[5] and int[7] provide some kind of polymorphism (and usually store it as a struct of size + data, to enable that). For example: you can define a first function that goes from t[N]->t pretty easily, and it wou…

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