Live data from Hacker News

A convenient untruth: Array notation in C is a lie

blog.feabhas.com

111–120 of 176 posts

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

#111
post #66

This stuff is all taught in 1st year of C.S. I hope that anyone who works with asm, C (and also C++) learned all this when he was still a kid

From my own experience and what I've seen from major US universities, this does not seem to be the case.

For intro (1st year-ish) CS, it looks like most places are teaching Python and C++, with some institutions (such as my own) using Java. An ACM article from 2014 actually has some numbers here. [0]

I graduated relatively recently with a bachelors degree, majoring in CS and Computer Engineering. I had only one course which actually used C, and that wasn't for my CS major. I've spent a fair amount of time since then doing low-level work on ARM micros, but definitely wasn't taught this in school.

[0] http://cacm.acm.org/blogs/blog-cacm/176450-python-is-now-the...

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

#112
post #21

The equivalent in C# always made more sense to me (not comparing memory or allocation model between the languages, but simply syntax in relation to a person reading it): int[] arr = new int[5]; // C# int arr[5]; // C The fact that the brackets go on the datatype always made more sense to me, after all, I want to refer to memory of a certain cell size (as indicated by int). I realize that there is a lot of stuff going…

It was always puzzling to me why people stick to char *foo instead of char* foo foo is obviously a pointer to the char, not a char. It has different size and behavior.

Otoh,

  *foo
is obviously a char. Do you put a space between dereference operator and its operand?

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

#114

Earlier quoted context omitted.

> 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. arr and arr2 are indeed distinct types. They are of type int[5] and int[7]. You can see this by checking that they have different sizes, or that `printf("%s\n", std::is_same ::value ? "true" : "false");` will…

Sizes aren't types though, and if I'm not mistaken that line of code is c++ no?

On older versions of clang (including macOS 10.10 / Xcode 7.2) and, I believe, GCC this code

  #include 
  #define is_same(T, x) _Generic((x), T: "true", default: "false")
  int main(void) {
    int arr[5];
    int arr2[7];
    printf("arr === int[5] -> %s\n", is_same(int[5], arr));
    printf("arr === int[7] -> %s\n", is_same(int[7], arr));
    printf("arr2 === int[5] -> %s\n", is_same(int[5], arr2));
    printf("arr2 === int[7] -> %s\n", is_same(int[7], arr2));
    return 0;
  }
produces

  arr === int[5] -> true
  arr === int[7] -> false
  arr2 === int[5] -> false
  arr2 === int[7] -> true
Unfortunately either clang or GCC (I can't remember) decided the obvious behavior was wrong and changed it so that _Generic behaved as-if array expressions decayed to pointers. The C11 specification for _Generic was insufficiently precise, and for various reasons both vendors and (IIRC) the C committee are going to go with the least common denominator approach (just treat them like pointers) for consistency.

So newer versions of clang and GCC print out all false.

But another way of showing that arrays are real types is with

  #include 
  #define countof(a) (sizeof (a) / sizeof *(a))
  int main(void) {
    int arr[5];
    int arr2[7];
    printf("countof(arr) -> %zu\n", countof(arr));
    printf("countof(*&arr) -> %zu\n", countof(*&arr));
    printf("countof(arr2) -> %zu\n", countof(arr2));
    printf("countof(*&arr2) -> %zu\n", countof(*&arr2));
    return 0;
  }
which produces

  countof(arr) -> 5
  countof(*&arr) -> 5
  countof(arr2) -> 7
  countof(*&arr2) -> 7
on all version of clang and GCC, and should on any other conformant C compiler. Although I would think that the simple sizeof proof should suffice to show that arrays are real types, notwithstanding that their evaluation rules are peculiar.

Alas, the disaster with _Generic and array expressions only proves that the situation is less than ideal. Although part of the problem is that _Generic was a novel language feature that didn't fit neatly into the historical translation phases. IMO C++ gets a lot of things wrong about C semantics, but apparently they got decltype right (presuming the behavior is a product of a clearer specification, and that behavior is consistent across implementations).

To be fair, although inelegant the compromise behavior for _Generic makes some sense. The principle use for _Generic is to implement crude function overloading. Because arrays always decay to pointers when passed to functions, it's convenient that _Generic would capture array expressions as pointers. OTOH, it makes some useful behaviors impossible. And the convenient behavior could have been had by manually coercing arrays to pointers using a trick like:

  #define decay(arr) ((0)? 0 : (arr))
  #define is_same(T, x) _Generic(decay(x), ...)

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

#115
post #112

Earlier quoted context omitted.

It was always puzzling to me why people stick to char *foo instead of char* foo foo is obviously a pointer to the char, not a char. It has different size and behavior.

Otoh, *foo is obviously a char. Do you put a space between dereference operator and its operand?

No, I don't. If foo is a (storage of) pointer to something, to get that something from memory I use dereference operator `* so

  *foo = 'a';
will write value of 'a' into memory at address that is in foo.

  type* foo;
is declaration and

  *foo
is dereferencing.

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

#116

Earlier quoted context omitted.

That's because [1] char* foo, bar; doesn't mean what one would expect it to mean. [1] as per Rule of Maximum Astonishment.

I find that char* foo = NULL; char bar = 0; are preferable. Is there any reason one would want to smash N declarations into one?

I prefer that as well, but because the other option is possible and sometimes used, a few prefer to put the star close to the name as to avoid mistakes.

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

#117
post #71

Why did people think otherwise? Chapter 5 of K&R [1] is titled Pointers and Arrays and basically explains that arrays and pointers are equivalent. [1]: https://www.amazon.com/Programming-Language-Brian-W-Kernigha...

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.

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

#118
I once proposed a backwards-compatible way out of this: "Safe Arrays for C"[1]. The fundamental problem with arrays in C is that compiler has no idea how big they are. My proposal was to replace

    int read(int fd, char buf[n], size_t n);
with a safe form

    int read(int n; int fd, char (&buf)[n], size_t n);
This says that the size of "buf" is "n", which comes in as another parameter. There are no array descriptors; the generated code for a call is the same. Thus, this is backwards-compatible, allowing mixing of "regular C" and "safe C" modules.

The programmer has to know how big the array is, after all. There must be some way to compute the array size from other variables or constants, or the program has no hope of working. All C needs is a way to allow the programmer to say that in the language. Then subscript checking is possible. Buffer overflows can be eliminated.

The required changes to C are minor. The big one is adding C++ references. Instead of passing a pointer to the first element of an array, you pass a reference to the array. Same object code, but now arrays are first-class objects.

This was discussed at length on the C standards digest back in 2012. After many revisions, the conclusion was that it was technically feasible, but too difficult politically.

[1] http://www.animats.com/papers/languages/safearraysforc43.pdf

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

#119
post #2

It's not that convenient an untruth seeing as these are probably some of the first things you learn in C, and some of the first gotchas that'll getcha. Having said that, the article was well written.

Some of the first things that people learn in C is the fallacy that "arrays and pointers are equivalent". An array is a series of contiguously laid-out objects that has a size known at compile time (except C99 VLAs). A pointer, on the other hand is merely a "single cell" that is supposed to contain an address and can be added/subtracted to, also dereferenced. The truth is that array names decay to pointers except whe…

Right, the fact that arrays aren't pointers is academic when they decay to pointers at the drop of a hat. In fact it's so easy to decay the array to a pointer that it is generally best to always treat it as a pointer lest you get burned later on during a code refactor. This mostly means never using sizeof() to get the size of an array.

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

#120

I once proposed a backwards-compatible way out of this: "Safe Arrays for C"[1]. The fundamental problem with arrays in C is that compiler has no idea how big they are. My proposal was to replace int read(int fd, char buf[n], size_t n); with a safe form int read(int n; int fd, char (&buf)[n], size_t n); This says that the size of "buf" is "n", which comes in as another parameter. There are no array descriptors; the ge…

> The fundamental problem with arrays in C is that compiler has no idea how big they are

Isn't that the compiler's choice, to not know how big they are? You could write a standards compliant implementation of C that did track how big arrays were if you wanted to couldn't you?

Post reply on HN