7 ms·
I like it, but the array details are a little bit off. An actual array does have a known size, that's why when given a real array `sizeof` can give the size of
by DSMan195276 4y ago
I like it, but the array details are a little bit off. An actual array does have a known size, that's why when given a real array `sizeof` can give the size of the array itself rather than the size of a pointer. There's no particular reason why C doesn't allow you to assign one array to another of the same length, it's largely just an arbitrary restriction. As you noted, it already has to be able to do this when assigning `struct`s.
Additionally a declared array such as `int arr[5]` does actually have the type `int [5]`, that is the array type. In most situations that decays to a pointer to the first element, but not always, such as with `sizeof`. This becomes a bit more relevant if you take the address of an array as you get a pointer to an array, Ex. `int (*ptr)[5] = &arr;`. As you can see the size is still there in the type, and if you do `sizeof *ptr` you'll get the size of the array.
- tmewett 4y agoInteresting, I didn't fully realise that. That it's arbitrary is annoying, I clearly had tried to rationalise it to myself! Thanks for the comments, will get around to amending
- emmelaich 4y agoHi, great article. Regarding char, I'd remark that getchar() etc return int so it can return -1 for EOF or error. I'm pretty sure this implies int as a declaration is always signed, but tbh I'm not completely sure!
- yakubin 4y agoint, as all other integer types except char, is indeed signed by default. Aside: signedness semantics of char is implementation-defined. However, the type char itself is always distinct from both signed char and unsigned char.
- dahfizz 4y agoAnother weird property about C arrays is that &arr == arr. The reference of an array is the pointer to the first element, which is what `arr` itself decays to. If arr was a pointer, &arr != arr.
- chasil 4y agoI think it is clearer to say that arr == &arr[0] but your mileage may vary.
- WalterBright 4y agoIs today international speak like a pirate day? arr arr arr
- naniwaduni 4y ago&arr is a pointer to the array. It will happen to point to the same place as the first element, but in fact they have different types, and e.g. (&arr)[0] == arr != arr[0].
- retrac 4y ago> it's largely just an arbitrary restriction Kind of. But the restriction is in keeping with the C philosophy of no hidden implementation magic. C has the same restriction on structs. That's the same question; an array of bytes of known size to the compiler it could easily abstract away. But assignment is always a very cheap operation in C. If we allow assigning to represent memcpy() that property is no longer true. Same reason why Rust requires you to .clone() so much. It could do many of the explicit copies transparently, but you might accidentally pass around a 4 terabyte array by value and not notice.
- mytherin 4y agoPerhaps I am missing something in the spec - but trying this in various compilers, it seems that you *can* assign structs holding arrays to one another, but you *cannot* assign arrays themselves. This compiles: struct BigStruct { int my_array[4]; }; int main() { struct BigStruct a; struct BigStruct b; b = a; } But this does not: int main() { int a[4]; int b[4]; b = a; } That seems like an arbitrary restriction to me.
- poepie 4y agoIn the first example a & b are variables, which can be assigned to each other. In the second a & b are pointers, but b is fixed, so you can not assign a value to it.
- yakubin 4y agoThey’re not pointers. sizeof a == 4*sizeof(int), not sizeof(int*).
- chipsa 4y agoThey're pointers, just weird ones. The compiler knows it's an array, so it gives the result of the actual amount of space it takes up. If you passed it into a function, and used the sizeof operator in the function, it'd give `sizeof(int *)`. Because sizeof is a compile-time operation, so the compiler still knows that info for your example.
- derefr 4y ago> There's no particular reason why C doesn't allow you to assign one array to another of the same length, it's largely just an arbitrary restriction. IIRC C has an informal guarantee that no primitive syntax will ever cause the CPU to do more than O(1) work at runtime. Assignment is always O(1), and therefore assignment is limited to scalars. If you need assignment that might do O(N) work, you need to call a stdlib function (memcpy/memmove) instead. If you need an allocation that might do O(N) work, you either need a function (malloc) or you need to do your allocation not-at-runtime, by structuring the data in the program's [writable] data segment, such that it gets "allocated" at exec(2) time. This is really one of the biggest formal changes between C and C++ — C++ assignment, and keywords `new` and `delete`, can both do O(N) work. (Before anyone asks: a declaration `int foo[5];` in your code doesn't do O(N) work — it just moves the stack pointer, which is O(1).)
- leni536 4y agoThis reasoning falls apart for structs with array members.
- DSMan195276 4y ago> Assignment is always O(1) This depends on what you consider to be O(1) - being that the size of the array is fixed it's by definition O(1) to copy it, but I might get your point. I think in general your point isn't true though, C often supports integer types that are too large to be copied in a single instruction on the target CPU, instead it becomes a multi-instruction affair. If you consider that to still be O(1) then I think it's splitting hairs to say a fixed-size array copy would be O(N) when it's still just a fixed number of instructions or loop iterations to achieve the copy. Beyond that, struct assignments can already generate loops of as large a size as you want, Ex: https://godbolt.org/z/8Td7PT4af https://godbolt.org/z/8Td7PT4af
- LegionMammal978 4y agoI think the meaning here is that assignment is never O(N) for any variable N computed at runtime. Of course, you can create arbitrarily large assignments at compile time, but this always has an upper bound for a given program.
- Jasper_ 4y agoI really wish that int arr[5] adopted the semantics of struct { int arr[5]; } -- that is, you can copy it, and you can pass it through a function without it decaying to a pointer. Right now in C: typedef uint32_t t1[5]; typedef struct { uint32_t arr[5]; } t2; void test(t1 a, t2 b) { t1 c; t2 d; printf("%d %d %d %d\n", sizeof(a), sizeof(b), sizeof(c), sizeof(d)); } will print 4, 20, 20, 20. I understand that array types having their sizes in their types was one of Kernighan's gripes with Pascal [0], which likely explains why arrays decay to pointers, but for those cases, I'd say you should still decay to a pointer if you really wanted to, with an explicit length parameter. [0] http://www.lysator.liu.se/c/bwk-on-pascal.html http://www.lysator.liu.se/c/bwk-on-pascal.html
- WalterBright 4y ago> I really wish that int arr[5] adopted the semantics of struct { int arr[5]; } You and me both. In fact, D does this. `int arr[5]` can be passed as a value argument to a function, and returned as a value argument, just as if it was wrapped in a struct. It's sad that C (and C++) take every opportunity to instantly decay the array to a pointer, which I've dubbed "C's Biggest Mistake": https://www.digitalmars.com/articles/C-biggest-mistake.html https://www.digitalmars.com/articles/C-biggest-mistake.html
- bombcar 4y agoThat would be a nice little "gcc addition" to the C standard, honestly. To bad they spend most their time doing whatever it is they do.
- maxdamantus 4y agoEarlier versions of gcc actually used to support this in a very restricted context in C90 (or maybe gnu89) mode: struct foo { int a[10]; }; struct foo f(void); int b[10]; b = f().a; In C90, you can't actually do anything with `f().a` because the conversion from array to pointer only happened to lvalues (`f().a` is not an lvalue), and assignment is not defined for array variables (though gcc allowed it). The meaning is changed in C90 so that non-lvalue arrays are also converted to pointers. gcc used to take this distinction into account, so the above program would compile in C90 mode but not in C99 mode. New versions of gcc seem to forbid array assignment in all cases. I think this quirk also means that it's technically possible to pass actual arrays to variadic functions in C90, since there was nothing to forbid the passing (it worked in gcc at least, though in strict C90, you wouldn't be able to use the non-lvalue array). In C99 and above, a pointer will be passed instead.
- a1369209993 4y ago> There's no particular reason why C doesn't allow you to assign one array to another of the same length Actually, there is a particular (though not necessarily good) reason, since that would require the compiler to either generate a loop (with conditional branch) for a (unconditional) assignment or generate unboundedly many assembly instructions (essentially a unrolled loop) for a single source operation. Of course, that stopped being relevant when they added proper (assign, return, etc) support for structs, which can embed arrays anyway, but that wasn't part of the language initially.
- pjmlp 4y agoIt was initially available in 1982, so plenty of time to add the other features. https://www.bell-labs.com/usr/dmr/www/chist.html https://www.bell-labs.com/usr/dmr/www/chist.html
- danoman 4y agoReturn pointer to array of 4 integers: int32_t (* bar(void))[4] { static int32_t u[4] = {1, 0, 1, 0}; return &u; } Return a pointer to a function taking a char: void f(char a) { // ... } void (* baz(void))(char) { return f; }
- wnoise 4y agoThis is where you really want to start using typedefs.
- Joker_vD 4y agoAnother one corner of the language where arrays actually being arrays is important is multidimensional array access: int arr[5][7]; arr[3][5] = 4; // equivalent to *(*(arr + 3) + 5) = 4; This works because (arr + 3) has type "pointer to int[7]", not "pointer to int". The resulting address computation is (char*)arr + 3 * sizeof(int[7]) + 5 * sizeof(int) == (char*)arr + 26 * sizeof(int) That's also another reason why types like "int [5][7][]" are legal but "int [5][][]" are not.
- ErikCorry 4y agoReally, are multidimensional arrays an important part of the language? The above code looks like it's indexing into an array of pointers. If you want a flat array, make a few inlined helper functions that do the multiplying and adding. Your code will be much cleaner and easier to understand.
- Joker_vD 4y agoOf course multidimensional arrays are an important part of the language, just as the ability to have structs inside structs. > The above code looks like it's indexing into an array of pointers. If you want a flat array, make a few inlined helper functions that do the multiplying and adding. Your code will be much cleaner and easier to understand. It is a "flat" array already, not an array of pointers: [0]. No need to write the code that compiler generates for you already. [0] https://godbolt.org/z/x3cPf3TvT https://godbolt.org/z/x3cPf3TvT
- ErikCorry 4y agoI am aware that it's flat already.
- int_19h 4y agoThe code above does not mention any pointer types, so why would you assume that it's indexing into an array of pointers? I can't think of any reason why get(a, i, j) is more readable than a[i][j].