Author Topic: New C23 working draft  (Read 12573 times)

0 Members and 6 Guests are viewing this topic.

Offline newbrainTopic starter

  • Super Contributor
  • ***
  • Posts: 1906
  • Country: se
New C23 working draft
« on: June 12, 2022, 07:47:12 am »
I know only a subset of a minority of a segment will be interested, but a new working draft for what is supposed to become C23 has been published on open-std.org the 8th of June.

You can find it here.

Quite a number of proposals have been included, from the top of my head:
- Remove K&R style function declarations
- Mandatory 2's complement representation for integers.
- Binary literals with 0b or 0B
- Digit separation character ' in literals as in 123'456'789
- unreachable() macro
- Optional VLAs (as in C17 and C11 but not C99) but mandatory variably modified types (change)
Nandemo wa shiranai wa yo, shitteru koto dake.
 
The following users thanked this post: Ed.Kloonk, jeremy, evb149, MK14, SiliconWizard

Offline jeremy

  • Super Contributor
  • ***
  • Posts: 1080
  • Country: au
Re: New C23 working draft
« Reply #1 on: June 12, 2022, 09:22:38 am »
Thank you for sharing, there are indeed a few of us out here interested in this stuff. I'm glad to see that C (and C++) are getting some effort put into them, I just hope the compliers catch up quickly.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17773
  • Country: fr
Re: New C23 working draft
« Reply #2 on: June 12, 2022, 06:24:30 pm »
Yup, I've been keeping an eye on this too.

Haven't seen a lot of significant changes though apart from some sugar coating and removing obsolete constructs.
The binary literals are cool but nothing groundbreaking (I've actually never used them even with compilers supporting them as an extension, I find them hard to read and easy to fuck up), the digit separation is nice though, but nothing essential.

The mandatory 2's complement triggers a "finally!" reaction, but while it's important for compiler writers and standardization, in practice, all C compilers I've ever dealt with used 2's complement. So...

All in all, nothing big really. IMHO.
I'm still waiting for the support of "modules" in C. And also maybe some better support for generic programming - as long as it's kept simple, we don't want C to become C++.

As a question, I wonder what is the use of variably modified types if VLAs are not supported. Could you explain?
 

Offline newbrainTopic starter

  • Super Contributor
  • ***
  • Posts: 1906
  • Country: se
Re: New C23 working draft
« Reply #3 on: June 12, 2022, 10:17:25 pm »
Quote
Could you explain?
I could, but then I'd have to kill you.

Jokes aside, IIUC, a vmt can be used as a function parameter, the actual argument can be a regular array.
This comes handy to pass multidimensional arrays without pointer contortions and it's a really welcome improvement..

See, e.g., Example 4 at the bottom of page 118.

Other things:
- bool, true, false (so bool.h is not needed to use them), static_assert, alignas, alignof are now first class keywords.
- the set of reserved identifier has been relaxed.
  Though everyone was doing that, me included, it was strictly speaking UB to use identifier such as strength, EMPLOYEE, total and so on, due to 7.31 that reserved a vast number of prefixes for "future library use" (for the examples: str to E). They are now only "potentially" reserved.
Note that I'm not talking about the more commonly known (double) underscore prefixed ones.

EtA:
Quote
I find them hard to read and easy to fuck up
Same here! (Apart for me eschewing extensions as much as possible).
But now, with the introduction of the digit separator, they become much more usable!
« Last Edit: June 12, 2022, 10:23:51 pm by newbrain »
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17773
  • Country: fr
Re: New C23 working draft
« Reply #4 on: June 13, 2022, 12:02:54 am »
Quote
Could you explain?
I could, but then I'd have to kill you.

Jokes aside, IIUC, a vmt can be used as a function parameter, the actual argument can be a regular array.
This comes handy to pass multidimensional arrays without pointer contortions and it's a really welcome improvement..

OK, so as I thought, there's no other cases of VMT in C at the moment than arrays with unspecified size. I can see the point, but I admit I absolutely never use array types as function parameters, and indexing a "multi-dimensional array" is kinda trivial, but why not.

Otherwise, yeah, the rest looks like sugar coating. So after 20 odd years of C99, they now feel safe to add 'bool' as a keyword instead of _Bool (and so on). Great.
 

Offline TheCalligrapher

  • Regular Contributor
  • *
  • Posts: 190
  • Country: us
Re: New C23 working draft
« Reply #5 on: June 13, 2022, 04:29:12 am »
As a question, I wonder what is the use of variably modified types if VLAs are not supported. Could you explain?

In a previous discussion here I have already provided a link to my detailed explanation on SO

https://stackoverflow.com/a/54163435/187690

In short, the key points are that:

1. VLAs and regular arrays are perfectly inter-compatible (as long as their sizes match), which means that you can write functions accepting VLAs, yet pass-in ordinary arrays
2. You can allocate VLAs in dynamic memory

In other words one can use the power of VLAs, yet never have to create VLAs "on the stack". (The latter is often [mis-]used as a major vector of criticism directed at VLAs. But in reality such criticism is completely misguided.)
« Last Edit: June 13, 2022, 04:35:54 am by TheCalligrapher »
 
The following users thanked this post: newbrain

Offline magic

  • Super Contributor
  • ***
  • Posts: 8058
  • Country: pl
Re: New C23 working draft
« Reply #6 on: June 13, 2022, 06:26:09 am »
Well, I am one of those VLA haters ;)


1. VLAs and regular arrays are perfectly inter-compatible (as long as their sizes match), which means that you can write functions accepting VLAs, yet pass-in ordinary arrays
2. You can allocate VLAs in dynamic memory
Not sure you can do 2 without resorting to 1?


And most importantly, none of that trickery buys you more than a better readable function declaration. Certainly not a guarantee of correctness of any sort, not even under the simplest of circumstances.
Code: [Select]
$ cat test.c
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

void test(int n, int x[n]) {
        for (int i = 0; i < 4; i++)
                printf("%d\n", x[i]);
}

int main() {
        int n = 3;
        int x[n];
        for (int i = 0; i < n; i++)
                x[i] = i;
        test(0, x);
}
$ gcc test.c -o test -Wall -Wextra
$ clang test.c -o test -Wall -Wextra
$ ./test
0
1
2
134517376


Usefulness is further limited by not being usable in data structures; you really need to pass all your dimensions as separate arguments.
Code: [Select]
$ cat test.c
struct string {
        unsigned n;
        char s[n];
}
$ gcc -c test.c
test.c:3:9: error: ‘n’ undeclared here (not in a function)
  char s[n];
« Last Edit: June 13, 2022, 06:29:52 am by magic »
 

Offline TheCalligrapher

  • Regular Contributor
  • *
  • Posts: 190
  • Country: us
Re: New C23 working draft
« Reply #7 on: June 13, 2022, 06:40:46 am »
Not sure you can do 2 without resorting to 1?

Um... It is not exactly clear to me what you mean

Code: [Select]
int n = 42, m = 25;
int (*a)[n][m] = malloc(sizeof *a);

Here I do 2 without resorting to 1.


Code: [Select]
$ cat test.c
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

void test(int n, int x[n]) {
        for (int i = 0; i < 4; i++)
                printf("%d\n", x[i]);
}

int main() {
        int n = 3;
        int x[n];
        for (int i = 0; i < n; i++)
                x[i] = i;
        test(0, x);
}
$ gcc test.c -o test -Wall -Wextra
$ clang test.c -o test -Wall -Wextra
$ ./test
0
1
2
134517376

I don't see what you are trying to demonstrate with this code. Also, declaring a VLA in a function parameter list does not override the "classic" rule: array declarations in function parameter lists are automatically replaced with pointer declarations. So, your function declaration is actually equivalent to

Code: [Select]
void test(int n, int *x)
I.e. the function does not really use VLA at all. Basically, your example does not demonstrate anything related to VLA.

You really need a multidimensional array as a parameter to make VLA have any tangible effect, as in my examples at the link.

Usefulness is further limited by not being usable in data structures; you really need to pass all your dimensions as separate arguments.

This is akin to saying that usefulness of a microscope is limited by not being able to hammer in nails.

Natural semantics of VLA does not imply ability to use them inside data structures. Everybody understands that VLAs are run-time allocated. Nobody expects to be able to use run-time allocated arrays as struct fields. Such expectations do not arise.

And most importantly, none of that trickery buys you more than a better readable function declaration.

Again, here's an example where VLA parameter declaration translates into tangible functionality

Code: [Select]
#include <stdio.h>

void test(unsigned n, unsigned m, int a[n][m])
{
  for (unsigned i = 0; i < n; ++i)
  {
    for (unsigned j = 0; j < m; ++j)
      printf("%2d ", a[i][j]);
    printf("\n");
  }
}

int main()
{
  int a[2][3] =
  {
    { 0, 1, 2 },
    { 3, 4, 5 }
  };
  test(2, 3, a);
 
  printf("\n");
   
  int b[4][6] =
  {
    { 0, 1, 2, 3, 4, 5 },
    { 6, 7, 8, 9, 10, 11 }, 
    { 12, 13, 14, 15, 16, 17 }, 
    { 18, 19, 20, 21, 22, 23 }
  };
  test(4, 6, b);
}

This is far beyond "a better readable function declaration". This is impossible to implement in C without VLA, i.e. in C it is impossible to work with plain multi-dimensional arrays in any reasonable way without VLA support. Which is what makes VLA (or, more precisely, VMT) critically important. That is why it becomes mandatory in C23.
« Last Edit: June 13, 2022, 07:01:26 am by TheCalligrapher »
 
The following users thanked this post: newbrain

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6398
  • Country: nz
Re: New C23 working draft
« Reply #8 on: June 13, 2022, 07:40:03 am »
Quote
This is impossible to implement in C without VLA, i.e. in C it is impossible to work with plain multi-dimensional arrays in any reasonable way without VLA support.

Code: [Select]
void test(unsigned n, unsigned m, void *p)
{
  int *a = p;
  for (unsigned i = 0; i < n; ++i)
  {
    for (unsigned j = 0; j < m; ++j)
      printf("%2d ", a[i*m+j]);
    printf("\n");
  }
}

Code: [Select]
$ ./vla_fake
 0  1  2
 3  4  5

 0  1  2  3  4  5
 6  7  8  9 10 11
12 13 14 15 16 17
18 19 20 21 22 23
$

WFM

Not type-safe, certainly, but I think it falls within the bounds of "reasonable".
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 8058
  • Country: pl
Re: New C23 working draft
« Reply #9 on: June 13, 2022, 07:54:53 am »
I don't see what you are trying to demonstrate with this code. Also, declaring a VLA in a function parameter list does not override the "classic" rule: array declarations in function parameter lists are automatically replaced with pointer declarations. So, your function declaration is actually equivalent to

Code: [Select]
void test(int n, int *x)
I.e. the function does not really use VLA at all. Basically, your example does not demonstrate anything related to VLA.
Well, I just wanted to confirm that "modern C" is still as primitive as you described ;)

Even though the VLAs know their size, there is not a slightest attempt at bound checking or tracking if you maintain information about their dimensions correctly, apparently not even as a warning in very obviously invalid cases.

You really need a multidimensional array as a parameter to make VLA have any tangible effect, as in my examples at the link.

This is far beyond "a better readable function declaration". This is impossible to implement in C without VLA, i.e. in C it is impossible to work with plain multi-dimensional arrays in any reasonable way without VLA support.
Okay, you have a point here.
(Though I would still prefer the C++ solution of simulated multi-dimensional arrays that know their size internally, instead of a bunch of function arguments passed around).

Usefulness is further limited by not being usable in data structures; you really need to pass all your dimensions as separate arguments.

This is akin to saying that usefulness of a microscope is limited by not being able to hammer in nails.

Natural semantics of VLA does not imply ability to use them inside data structures. Everybody understands that VLAs are run-time allocated. Nobody expects to be able to use run-time allocated arrays as struct fields. Such expectations do not arise.

Right, it wasn't a good example. But this still doesn't work and I hope you appreciate that it could be useful:
Code: [Select]
struct matrix {
        const unsigned n;
        const unsigned m;
        float (*x)[n][m];
};

int main() {
        int n, m;
        float (*x)[n][m] = malloc(sizeof(*x));
        struct matrix mat = {n, m, x};
}

It seems I would need to declare the array as simply *x and then cast it to (*x)[n][m] in each function that works with my struct.

Code: [Select]
      printf("%2d ", a[i*m+j]);
Not type-safe, certainly, but I think it falls within the bounds of "reasonable".
Yes, you can, but I agree with TheCalligrapher here - it really sucks.
 

Offline newbrainTopic starter

  • Super Contributor
  • ***
  • Posts: 1906
  • Country: se
Re: New C23 working draft
« Reply #10 on: June 13, 2022, 08:48:35 am »
Not type-safe, certainly, but I think it falls within the bounds of "reasonable".
Feasible, of course, but exactly what I referred to as "pointer contortions":
* the need to use an "universal" void* or, alternatively, cast the actual argument or pass the address of the first element etc.
* the need to explicitly make your own offset calculation, lowering the abstraction, as you just see an unravelled array and need to consider its memory layout (fixed and known, but still...).
ugly, less safe (as in more prone to error) and less understandable (especially with more than two axis).
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6398
  • Country: nz
Re: New C23 working draft
« Reply #11 on: June 13, 2022, 10:31:47 am »
Not type-safe, certainly, but I think it falls within the bounds of "reasonable".
Feasible, of course, but exactly what I referred to as "pointer contortions":
* the need to use an "universal" void* or, alternatively, cast the actual argument or pass the address of the first element etc.
* the need to explicitly make your own offset calculation, lowering the abstraction, as you just see an unravelled array and need to consider its memory layout (fixed and known, but still...).
ugly, less safe (as in more prone to error) and less understandable (especially with more than two axis).

The safety, in a practical sense, is very little different. The most cursory unit test of the function will show whether the address calculation is correct or not.

There is no difference in efficiency. The multiply in a loop will be strength-reduced to an add in either case.

The much bigger worry is whether the function -- VLT or not -- is called correctly. There is absolutely no check in either version that the correct bounds are passed.

In particular, it would be very easy to accidentally swap the dimensions. The VLT version has no protection against swapped or incorrect dimensions.
 

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 11140
  • Country: fi
Re: New C23 working draft
« Reply #12 on: June 13, 2022, 11:14:37 am »
The binary literals are cool but nothing groundbreaking (I've actually never used them even with compilers supporting them as an extension, I find them hard to read and easy to fuck up), the digit separation is nice though, but nothing essential.

Combined, they are pretty great. I use binary literals quite a bit but the biggest issue is sheer number of digits making eyeballing byte/nibble boundaries difficult, and more prone to errors than hexadecimal. Adding digit separation makes binary literals great again. 0b0101'1111'0110'1001 looks quite nice to me. But yeah, small stuff.
« Last Edit: June 13, 2022, 11:18:45 am by Siwastaja »
 
The following users thanked this post: newbrain

Offline TheCalligrapher

  • Regular Contributor
  • *
  • Posts: 190
  • Country: us
Re: New C23 working draft
« Reply #13 on: June 13, 2022, 01:14:14 pm »
Quote
This is impossible to implement in C without VLA, i.e. in C it is impossible to work with plain multi-dimensional arrays in any reasonable way without VLA support.

Code: [Select]
void test(unsigned n, unsigned m, void *p)
{
  int *a = p;
  for (unsigned i = 0; i < n; ++i)
  {
    for (unsigned j = 0; j < m; ++j)
      printf("%2d ", a[i*m+j]);
    printf("\n");
  }
}

Code: [Select]
$ ./vla_fake
 0  1  2
 3  4  5

 0  1  2  3  4  5
 6  7  8  9 10 11
12 13 14 15 16 17
18 19 20 21 22 23
$

WFM

Not type-safe, certainly, but I think it falls within the bounds of "reasonable".

Where is the rest of the code? What do the calls to `test` look like?

If you are implying that this can work with my original `main`, then no, it doesn't. You are not allowed to reinterpret/access a two-dimensional [N][M] array as a one-dimensional [N*M] array in C. The behavior is undefined.
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 8058
  • Country: pl
Re: New C23 working draft
« Reply #14 on: June 13, 2022, 01:51:15 pm »
Are you sure of that?

There are no two dimensional arrays in C. Simply taking a[0] in your example gives type int[2] which can be passed to a function demanding int[] without even casting and given the well-defined memory layout of arrays I'm not sure at which point the code is supposed to break. Or which part of the standard says that you aren't supposed to do so. FWIW, I'm pretty sure I have seen such code in the wild.
 

Offline TheCalligrapher

  • Regular Contributor
  • *
  • Posts: 190
  • Country: us
Re: New C23 working draft
« Reply #15 on: June 13, 2022, 02:08:47 pm »
Are you sure of that?

There are no two dimensional arrays in C.

??? Yes, there are.

Simply taking a[0] in your example gives type int[2] which can be passed to a function demanding int[] without even casting

Firstly, `a[0]` in my example is `int[3]`.

Secondly, of course, it can be passed without casting. `a[0]` is an one-dimensional array of type `int[3]`. It can be passed to a function expecting a one-dimensional array (i.e. a pointer, in my case). Nothing unusual here. However, the basic language rules still apply: you are not allowed to access beyond the boundary of that one-dimensional array.

and given the well-defined memory layout of arrays I'm not sure at which point the code is supposed to break.

The "well-defined memory layout" does not matter. What matters is that you are working with an `int[3]` array and language rules do not permit access beyond its boundary. Otherwise the behavior is undefined. The compiler is allowed to assume that undefined behavior never happens and translate (optimize) the code under that assumption. For example, the compiler is allowed to, say, inline the `test` call and, say, unroll the cycle to 3 iterations tops (since there "can't possibly be" more than 3).

FWIW, I'm pretty sure I have seen such code in the wild.

Of course. This has always been a hack that we had to employ when working with multi-dimensional arrays because there simply is no other way. As I said above, this is impossible to implement properly without VLA support.

There are many hack that exists "in the wild". Some of them have been killed for good at some point by the above "undefined behavior never happens" optimization rule (e.g. strict overflow semantics, strict aliasing semantics, returning null as pointers to locals, and so on and so forth). Some still survive...
« Last Edit: June 13, 2022, 02:11:38 pm by TheCalligrapher »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: New C23 working draft
« Reply #16 on: June 13, 2022, 02:18:19 pm »
Whenever I end up using nontrivial 2D arrays (for example linear algebra stuff), I end up having to use origin[row*rowstride + col*colstride] anyway.
It might seem like a slowdown, but is actually at the core of why naïve Fortran yields so good results.  I haven't microbenchmarked it either, but I suspect the "extra" multiplication is insignificant compared to memory bandwidth.

For trivial arrays, I tend to use inner loops that access consecutive memory via a pointer.  The cost of setting up that pointer just outside the inner loop gets lost in the noise.

As to the C23 draft, nothing pokes my eye immediately; looks like to be quite bog-standard (pun not intended) gradual development, and not pushing for anything "new"/"unexpected".  (Which is a good thing: the C standard should codify expected and existing practice, not dictate new behaviour.)

You are not allowed to reinterpret/access a two-dimensional [N][M] array as a one-dimensional [N*M] array in C. The behavior is undefined.
I am not so sure that array bounds actually apply that way here (making the behaviour undefined).

The way I interpret C99/C11/C17 6.5.2.1 paragraphs 2 to 4 is that the behaviour is fully defined, albeit implicitly, because each successive indexing converts the expression to a lower-dimensional array with pointer semantics.  In short, because m[x][y] is identical to (m[x])[y].

In particular, consider type punning via an union:
    union {
        type a[N*M];
        type b[N][M];
    } foo;
Assuming 0 <= n < N and 0 <= m < M, are (foo.a[n*M+m]) and (foo.b[n][m]) equivalent expressions accessing the same element, or not?
 

Offline newbrainTopic starter

  • Super Contributor
  • ***
  • Posts: 1906
  • Country: se
Re: New C23 working draft
« Reply #17 on: June 13, 2022, 03:10:46 pm »
Are you sure of that?
From 6.5 Expressions in C11:
Quote
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
— a type that is the signed or unsigned type corresponding to the effective type of the object,
— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
— a character type
Aliasing an array using an incompatible type (arrays of different sizes and element types are not compatible) does not fall in any of the above cases, so, going against a "shall" outside of a Constraints section it's automatically UB.

Similarly, in Appendix J, J.2 Undefined Behavior:
Quote
— An array subscript is out of range, even if an object is apparently accessible with the given subscript (as in the lvalue expression a[1][7] given the declaration int a[4][5]) (6.5.6).

Another case where "it works" "everybody does it" but still non conforming code.
There's a correct way to do it since C99, and C23, untying VLAs from VMT, improves its usability.
« Last Edit: June 13, 2022, 07:15:11 pm by newbrain »
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6398
  • Country: nz
Re: New C23 working draft
« Reply #18 on: June 13, 2022, 09:36:53 pm »
Quote
This is impossible to implement in C without VLA, i.e. in C it is impossible to work with plain multi-dimensional arrays in any reasonable way without VLA support.

Code: [Select]
void test(unsigned n, unsigned m, void *p)
{
  int *a = p;
  for (unsigned i = 0; i < n; ++i)
  {
    for (unsigned j = 0; j < m; ++j)
      printf("%2d ", a[i*m+j]);
    printf("\n");
  }
}

Code: [Select]
$ ./vla_fake
 0  1  2
 3  4  5

 0  1  2  3  4  5
 6  7  8  9 10 11
12 13 14 15 16 17
18 19 20 21 22 23
$

WFM

Not type-safe, certainly, but I think it falls within the bounds of "reasonable".

Where is the rest of the code? What do the calls to `test` look like?

I did not change the rest of your code.

Quote
If you are implying that this can work with my original `main`, then no, it doesn't. You are not allowed to reinterpret/access a two-dimensional [N][M] array as a one-dimensional [N*M] array in C. The behavior is undefined.

Sure does work. I tried it on arm64, amd64, and riscv64 machines.

The only thing that could make it not work is if rows are padded, in which case m should be rounded up in some way before multiplying. If that happens on some platform you have then 1) that's weird, and 2) that can easily be incorporated with some #if.

The only time I could ever see that happening is if the array element size is smaller than the word size.
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 8058
  • Country: pl
Re: New C23 working draft
« Reply #19 on: June 14, 2022, 10:29:50 am »
There are no two dimensional arrays in C.
??? Yes, there are.
... in languages like BASIC, Fortran or Matlab.

C has no first class two dimensional arrays, only arrays of arrays are sometimes known as such, and all the usual array rules apply to them.

An argument in favor of such conversion is:
1. we are allowed to convert an array of arrays to a pointer to an array and use that as we please
2. we are allowed to convert an array to a pointer to its individual element and use that as we please

I understand your objection to be that the scalar pointer obtained in step 2 is only permitted to refer to elements of the particular "inner" array obtained in step 1 and that the implementation is permitted to blindly assume that it is so.

The "well-defined memory layout" does not matter. What matters is that you are working with an `int[3]` array and language rules do not permit access beyond its boundary. Otherwise the behavior is undefined. The compiler is allowed to assume that undefined behavior never happens and translate (optimize) the code under that assumption. For example, the compiler is allowed to, say, inline the `test` call and, say, unroll the cycle to 3 iterations tops (since there "can't possibly be" more than 3).
I agree that my "solution" was a blatant cheat on the type system and the above is a potential concern.

So let's go to the original code posted by Bruce, where the full array is simply passed as a void pointer. Now, the compiler would need to somehow conclude that the void* converted to int* by the calle somehow is a pointer to one of the inner arrays, rather than the full array of arrays which has been passed by the caller (note that when the array of arrays decays to a pointer, the pointer can still be used by the callee to access all of the array, otherwise memcpy wouldn't work). And we could go further, using memcpy to exctract individual elements and only then converting them to int.

From 6.5 Expressions in C11:
Quote
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
— a type that is the signed or unsigned type corresponding to the effective type of the object,
— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
— a character type
Aliasing an array using an incompatible type (arrays of different sizes and element types are not compatible) does not fall in any of the above cases, so, going against a "shall" outside of a Constraints section it's automatically UB.
[/quote]
I'm not 100% sure what this rule means in the context of accessing array members. Naive reading seems to imply that no access to an individual array member through a pointer is legal, if we read "the object" as "the array" and consider such member access to be an array access too.

Similarly, in Appendix J, J.2 Undefined Behavior:
Quote
— An array subscript is out of range, even if an object is apparently accessible with the given subscript (as in the lvalue expression a[1][7] given the declaration int a[4][5]) (6.5.6).
We are not accessing anything out of declared bounds, but declaring no bounds on an alias to an object declared as a particular array type elsewhere.

If there is something I would be concerned about in practical terms, it would be alias analysis.

Sure does work. I tried it on arm64, amd64, and riscv64 machines.
The discussion is whether it is permitted to stop working tomorrow out of a sudden.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6398
  • Country: nz
Re: New C23 working draft
« Reply #20 on: June 14, 2022, 11:07:50 am »
Sure does work. I tried it on arm64, amd64, and riscv64 machines.
The discussion is whether it is permitted to stop working tomorrow out of a sudden.

That's what unit tests are for, possibly run up front in a ./configure step.

I think that, as with very many UB things, it's not about future machines or compilers, but ones in the distant past, or highly specialised ones.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: New C23 working draft
« Reply #21 on: June 14, 2022, 12:21:02 pm »
That's what unit tests are for, possibly run up front in a ./configure step.
Or as an optional build-check target, compiling a set of test programs that verify expected behaviour.

I kinda-sorta prefer the separate build check target, because that way one can still cross-compile the sources.  (That is, one can cross-compile the tests, and only need to run the tests on the target architecture to verify the compiler operation.)
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 9049
  • Country: us
    • SiliconValleyGarage
Re: New C23 working draft
« Reply #22 on: June 14, 2022, 12:55:06 pm »
no more forward declaration.
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 8058
  • Country: pl
Re: New C23 working draft
« Reply #23 on: June 14, 2022, 01:44:17 pm »
The discussion is whether it is permitted to stop working tomorrow out of a sudden.
That's what unit tests are for, possibly run up front in a ./configure step.
Unit testing may easily miss compiler bugs and overzealous "optimization".
Your trivial test case is not the same code which will run in production.

Things may get even worse nowadays with link time optimizations.

I think that, as with very many UB things, it's not about future machines or compilers, but ones in the distant past, or highly specialised ones.
Well, except for that time when GCC removed NULL checks from Linux on pointers that have been (accidentally) dereferenced earlier, creating serious kernel data corruption vulnerabilities visible only in the generated machine code.

I also recall fixing some open source code which interpreted float as int by means of pointers rather than union and stopped working in GCC 4.0. There may still be some codebases which pass -fno-strict-aliasing to GCC.
 

Offline TheCalligrapher

  • Regular Contributor
  • *
  • Posts: 190
  • Country: us
Re: New C23 working draft
« Reply #24 on: June 14, 2022, 01:53:44 pm »
You are not allowed to reinterpret/access a two-dimensional [N][M] array as a one-dimensional [N*M] array in C. The behavior is undefined.
I am not so sure that array bounds actually apply that way here (making the behaviour undefined).

The way I interpret C99/C11/C17 6.5.2.1 paragraphs 2 to 4 is that the behaviour is fully defined, albeit implicitly, because each successive indexing converts the expression to a lower-dimensional array with pointer semantics.  In short, because m[x][y] is identical to (m[x])[y].

I don't see how this equivalence can possibly save the day here.

The issue is essentially the same as with the proverbial "struct hack". The "flexible array member" declaration with `[]` was introduced into the language specifically because all "hackish" variants with `[0]`, `[1]` or `[a lot]` suffered from various array access problems. The allegedly "cleanest" `[1]` variant was problematic because it declared an array of size 1 and then accessed beyond its bounds.

Again, the standard is pretty specific about it: if an array object is declared with a specific size, the language prohibits you from accessing beyond its boundary, i.e. use pointer arithmetic that crosses that boundary. No exceptions are made for sub-arrays within a multi-dimensional array.

A similar/related problem would arise in a case like this

Code: [Select]
int main()
{
  int a[][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
 
  for (const int *p = &a[0][0]; p < &a[0][3]; ++p)
    printf("%d ", *p);

  printf("\n");

  for (const int *p = &a[0][0]; p < &a[1][0]; ++p)
    printf("%d ", *p);
   
  printf("\n");
}

Both cycles work the same in practice, but the second one is undefined. Even though you can "prove" that `&a[1][0]` is the same as `&a[0][3]`, you are still not allowed to compare `p` to `&a[1][0]`: pointer `p` works over `a[0]`, while `&a[1][0]` is obtained though a completely different array `a[1]`. These two pointers are incomparable.
 
The following users thanked this post: newbrain


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf