Discussion:
Array parameters
(too old to reply)
s***@gmail.com
2017-03-26 12:47:21 UTC
Permalink
So lately I've came up to this again - and to be quite honest recently I've kinda accepted the form f(int a[]) as a shorthand for f(int *a), as part of what the C language is (the same way it was natural for me, before, to know that a file scope declaration using the static storage specifier indicates internal linkage but a block scope one, using the same identifier - storage duration).

However I still don't think this property, this shortcut for writing complex otherwise declarations (imagine if you want to pass a pointer to array) should be something more than this - a shortcut.

What I feel like is that we must banish all other forms except the way an "incomplete array of ..." can be declared as a function parameter to actually mean "pointer to ...". I also feel like we should allow this only in a direct declarator. So for example f(int (a)[]) would be a constraint violation.

The constraint violation should also apply for parameter declarations using a typedef name to specify the array type.


Bottom line:

I see the benefits of using [] as an alternative syntax for * when writing pointer to arrays (useful when passing multidimensional arrays) - consider writing f(int (*p)[2]) vs f(int p[][2]) - (I feel like the second variant is easier to edit.) However I feel like it should be implemented rather as a simple text replacement than whole type system change. Now I know that the preprocessor is another monster but I doubt that implementing a check for direct array declarator of incomplete type will be that expensive to the parser.


I doubt such a change will be any hassle.

The only possible problem I see is if someone has done this:

typedef int tarr[2][2];

int f(tarr par)
{
//... use pointer to multi-dimensional array first element
}

int main()
{
tarr ar; //declare multi-dimensional array

f(ar); //pass pointer to multi-dimensional array first element
}

Because the above can be kinda useful I guess.

If you think that any of the following declarations:

int f(int p[2]), f(int p[*]), f(int p[224]), f(int *p);

as a mean to declare the same type are anything but pure junk - then you may just skip this post and continue writing in C\C++ and may the programming gods be with you.
BartC
2017-03-26 14:32:29 UTC
Permalink
Post by s***@gmail.com
int f(int p[2]), f(int p[*]), f(int p[224]), f(int *p);
I thought the repeated f's were just for your example but this actually
compiles! (On 4 of my 6 compilers anyway.)

I'd never come across the p[*] form before. The specs say that it means
p is a VLA, in that case I'm surprised it's compatible with all the
others, assuming VLAs are meaningful for function parameters. But if
not, then what is the purpose?

(BTW there seem to be an unlimited number of ways of writing compatible
f-declarations, such as f(int signed (((((* const const)))))), plus
variations with typedefs, macros and different parameter names. All
jolly good.)
--
bartc
s***@gmail.com
2017-03-26 16:37:54 UTC
Permalink
Post by BartC
Post by s***@gmail.com
int f(int p[2]), f(int p[*]), f(int p[224]), f(int *p);
I thought the repeated f's were just for your example but this actually
compiles! (On 4 of my 6 compilers anyway.)
I'd never come across the p[*] form before. The specs say that it means
p is a VLA, in that case I'm surprised it's compatible with all the
others, assuming VLAs are meaningful for function parameters. But if
not, then what is the purpose?
(BTW there seem to be an unlimited number of ways of writing compatible
f-declarations, such as f(int signed (((((* const const)))))), plus
variations with typedefs, macros and different parameter names. All
jolly good.)
--
bartc
Of-course it does - don't you remember linkage? My point is that the only people writing the size of such pseudo arrays are people who doesn't really understand C.
Ben Bacarisse
2017-03-26 21:27:34 UTC
Permalink
BartC <***@freeuk.com> writes:
<snip>
Post by BartC
I'd never come across the p[*] form before. The specs say that it
means p is a VLA, in that case I'm surprised it's compatible with all
the others, assuming VLAs are meaningful for function parameters. But
if not, then what is the purpose?
It is there so you can write a prototype without names. Where the
definition might be

bool invert(int n, int m, double matrix[n][m]) {
...
}

Only the m is significant for the type here. I could have written

bool invert(int n, int m, double matrix[][m]) {
...
}

or used a pointer type explicitly.

A prototyped declaration can be

bool invert(int n, int m, double matrix[n][m]);
bool invert(int, int m, double matrix[][m]);
bool invert(int, int, double matrix[][*]);

and you can use the * even where it's not strictly needed:

bool invert(int, int, double matrix[*][*]);

<snip>
--
Ben.
BartC
2017-03-26 21:49:51 UTC
Permalink
Post by Ben Bacarisse
<snip>
Post by BartC
I'd never come across the p[*] form before. The specs say that it
means p is a VLA, in that case I'm surprised it's compatible with all
the others, assuming VLAs are meaningful for function parameters. But
if not, then what is the purpose?
It is there so you can write a prototype without names. Where the
definition might be
bool invert(int n, int m, double matrix[n][m]) {
...
}
Only the m is significant for the type here. I could have written
bool invert(int n, int m, double matrix[][m]) {
...
}
or used a pointer type explicitly.
A prototyped declaration can be
bool invert(int n, int m, double matrix[n][m]);
bool invert(int, int m, double matrix[][m]);
bool invert(int, int, double matrix[][*]);
bool invert(int, int, double matrix[*][*]);
OK, thanks. So for the 'f' prototypes in the OP's example, I guess the
'*' in the '[*]' can just be ignored.
--
Bartc
s***@gmail.com
2017-03-31 22:43:19 UTC
Permalink
Here is the OP (if anyone is interested):


So lately I've came up to this again - and to be quite honest recently I've kinda accepted the form f(int a[]) as a shorthand for f(int *a), as part of what the C language is (the same way it was natural for me, before, to know that a file scope declaration using the static storage specifier indicates internal linkage but a block scope one, using the same identifier - storage duration).

However I still don't think this property, this shortcut for writing complex otherwise declarations (imagine if you want to pass a pointer to array) should be something more than this - a shortcut.

What I feel like is that we must banish all other forms except the way an "incomplete array of ..." can be declared as a function parameter to actually mean "pointer to ...". I also feel like we should allow this only in a direct declarator. So for example f(int (a)[]) would be a constraint violation (maybe we could make use of that form in the future?).

The constraint violation should also apply for parameter declarations using a typedef name to specify the array type.


Bottom line:

I see the benefits of using [] as an alternative syntax for * when writing pointer to arrays (useful when passing multidimensional arrays) - consider writing f(int (*p)[2]) vs f(int p[][2]) - (I feel like the second variant is easier to edit.) However I feel like it should be implemented rather as a simple text replacement than whole type system change. Now I know that the preprocessor is another monster but I doubt that implementing a check for direct array declarator of incomplete type will be that expensive to the parser.
bartc
2017-03-31 23:28:26 UTC
Permalink
Post by s***@gmail.com
So lately I've came up to this again - and to be quite honest recently I've kinda accepted the form f(int a[]) as a shorthand for f(int *a), as part of what the C language is (the same way it was natural for me, before, to know that a file scope declaration using the static storage specifier indicates internal linkage but a block scope one, using the same identifier - storage duration).
However I still don't think this property, this shortcut for writing complex otherwise declarations (imagine if you want to pass a pointer to array) should be something more than this - a shortcut.
Have you only just discovered that C type declarations are a nightmare?
Post by s***@gmail.com
What I feel like is that we must banish all other forms except the way an "incomplete array of ..." can be declared as a function parameter to actually mean "pointer to ...". I also feel like we should allow this only in a direct declarator. So for example f(int (a)[]) would be a constraint violation (maybe we could make use of that form in the future?).
The constraint violation should also apply for parameter declarations using a typedef name to specify the array type.
I see the benefits of using [] as an alternative syntax for * when writing pointer to arrays (useful when passing multidimensional arrays) - consider writing f(int (*p)[2]) vs f(int p[][2]) - (I feel like the second variant is easier to edit.) However I feel like it should be implemented rather as a simple text replacement than whole type system change. Now I know that the preprocessor is another monster but I doubt that implementing a check for direct array declarator of incomplete type will be that expensive to the parser.
You have to be careful here: while C is capable of using genuine
'pointer-to-array-of-T' types, most such uses are actually coded as
'pointer-to-T'.

One reason is that pointer-to-T types can be indexed as though they were
ordinary arrays: A[i], but a pointer-to-array-of-T has to be indexed as
(*A)[i]. In other words, you have to acknowledge that it's a pointer by
dereferencing it, but that seems to go against the C style rules by
being too explicit; you don't want people to know what's going on!

(I normally code in another low-level language which handles this stuff
properly. You have T, arrays of T, pointer to T and pointer to arrays of
T. You can only index arrays, not pointers. You can only dereference
pointers, not arrays.

That traps a lot of errors that are possible in C, and also makes it
crystal clear what is going on: is that A[i][j] an array of arrays, or
is it an array of pointers, or pointer to array or pointer to pointer?
Well, when I write it, is /has/ to be array of arrays.

But as I said you can code this way in C too by following certain
guidelines, but the code is uglier. And would be non-idiomatic.)
--
bartc
j***@verizon.net
2017-04-01 01:20:57 UTC
Permalink
On Friday, March 31, 2017 at 7:28:12 PM UTC-4, bartc wrote:
...
Post by bartc
You have to be careful here: while C is capable of using genuine
'pointer-to-array-of-T' types, most such uses are actually coded as
'pointer-to-T'.
One reason is that pointer-to-T types can be indexed as though they were
ordinary arrays: A[i], but a pointer-to-array-of-T has to be indexed as
(*A)[i]. ...
Incorrect, it's perfectly legal to index it as A[0][i].
Post by bartc
... In other words, you have to acknowledge that it's a pointer by
dereferencing it,
Keep in mind that the ONLY thing you can subscript in C are pointers. It might seem that you can also subscript arrays, but that's just because lvalues of array type are automatically converted, in most contexts, into a pointer to the first element of the array. Therefore, dereferencing an expression by following it with a sub-script "acknowledges" that the expression has a pointer value just as strongly as prefixing it with *.
bartc
2017-04-01 09:37:41 UTC
Permalink
Post by j***@verizon.net
...
Post by bartc
You have to be careful here: while C is capable of using genuine
'pointer-to-array-of-T' types, most such uses are actually coded as
'pointer-to-T'.
One reason is that pointer-to-T types can be indexed as though they were
ordinary arrays: A[i], but a pointer-to-array-of-T has to be indexed as
(*A)[i]. ...
Incorrect, it's perfectly legal to index it as A[0][i].
OK, it has to be indexed as (*A)[i] for it to follow the
'pointer-to-array-of-T' model that you intend.

That it can also be accessed as A[i][j] (so it appears to be array of
array although only one is actually present), or as A[0][i] to at least
make the code correct if not less confusing, or as just A[i] (because
the type can also be regarded as pointer-to-U, where U array-of-T,
although this has to be in the right type context) just tends to support
my assertion that the type system is a 'nightmare'.

But as I said, you can cut through all this by following guidelines:

Access only as:

T T A
pointer to T T* *A
array of T T[] A[i]
array of array of T T[][] A[i][j]
pointer to pointer to T T** **A
pointer to array of T T(*)[] (*A)[i]
array of pointer to T T*[] *A[i]

array of T as param T[] Really means T*
array of array of T as param T[][] Really means T*[]
... I think

But as I also said, it's uglier especially for the simple cases of
pointer to array of T, which are more idiomatically handled as just
pointer-to-T.
Post by j***@verizon.net
Keep in mind that the ONLY thing you can subscript in C are pointers.
It might seem that you can also subscript arrays, but that's just because lvalues of array type are automatically converted, in most contexts, into a pointer to the first element of the array. Therefore, dereferencing an expression by following it with a sub-script "acknowledges" that the expression has a pointer value just as strongly as prefixing it with *.
That's not how some people's minds work.
--
bartc
j***@verizon.net
2017-04-01 14:32:16 UTC
Permalink
Post by bartc
Post by j***@verizon.net
...
Post by bartc
You have to be careful here: while C is capable of using genuine
'pointer-to-array-of-T' types, most such uses are actually coded as
'pointer-to-T'.
One reason is that pointer-to-T types can be indexed as though they were
ordinary arrays: A[i], but a pointer-to-array-of-T has to be indexed as
(*A)[i]. ...
Incorrect, it's perfectly legal to index it as A[0][i].
OK, it has to be indexed as (*A)[i] for it to follow the
'pointer-to-array-of-T' model that you intend.
That it can also be accessed as A[i][j] (so it appears to be array of
array although only one is actually present),
It is an array - of length one. It's an inherent feature (NOT a bug) of C that,
as far as pointer arithmetic is concerned, any single object of a given type
can be treated as the first and only element of a 1-element array of that type.
Unless you're going to forbid 1-element arrays, that's a reasonable approach to
take. It makes it possible to write routines that can handle arbitrary length
arrays, and to pass to such a routine a pointer to a single object, and have it
work correctly without any need to re-write the interface of the routine.

...
Post by bartc
Post by j***@verizon.net
Keep in mind that the ONLY thing you can subscript in C are pointers.
It might seem that you can also subscript arrays, but that's just because
lvalues of array type are automatically converted, in most contexts, into a
pointer to the first element of the array. Therefore, dereferencing an
expression by following it with a sub-script "acknowledges" that the
expression has a pointer value just as strongly as prefixing it with *.
That's not how some people's minds work.
True, and people who find such simple concepts hard to deal with should really
not be using C, but some other language that is a better fit to their
prejudices.
Keep in mind that C is still one of the most popular programming languages, and
has been for a long time. I was shocked to see that C's TIOBE index <https://www.tiobe.com/tiobe-index/> has shown a rapid decline starting in
2016, but it's still in second place, and it has been either first or second
place for several decades. You might argue with the TIOBE methodology (and I've
no intention of joining such an argument), but even with poor methodology, C
wouldn't even rank in 2nd place unless it were a pretty popular language.
I won't claim that everyone programming in C understands these concepts as well
as I do, but most of them find those concepts easier to understand than you do,
or they wouldn't be able to hold a job as a C programmer.
bartc
2017-04-01 15:32:32 UTC
Permalink
Post by j***@verizon.net
Post by bartc
That's not how some people's minds work.
True, and people who find such simple concepts hard to deal with should really
not be using C, but some other language that is a better fit to their
prejudices.
That's like having to drive instead of fly because you don't agree that
what the airlines call 'coffee' is coffee.

The problem is that the A[i] notation looks so remarkably like
subscripting that it can be mistaken for scripting.

There're also the problem that the same A[i] notation is used both when
A starts off as an actual array, as when it starts as a pointer. This
rapidly gets confusing with extra dimensions. The use of pointers
usually requires that some allocation or setup is done, but the fact
that explicit pointers are being used is now hidden.
Post by j***@verizon.net
I won't claim that everyone programming in C understands these concepts as well
as I do, but most of them find those concepts easier to understand than you do,
or they wouldn't be able to hold a job as a C programmer.
I don't know how C managed to get to its current position (perhaps a
lack of competition among systems programming languages). But it's more
likely to have been in spite of its arcane type system rather than
because of it.
--
Bartc
j***@verizon.net
2017-04-01 16:50:47 UTC
Permalink
Post by bartc
Post by j***@verizon.net
Post by bartc
That's not how some people's minds work.
True, and people who find such simple concepts hard to deal with should really
not be using C, but some other language that is a better fit to their
prejudices.
That's like having to drive instead of fly because you don't agree that
what the airlines call 'coffee' is coffee.
You can fly without drinking the coffee (I hate coffee, and that has never kept
me from flying). You can't use C effectively without a good understanding of the
the way it handles arrays and pointers.

Keep in mind that this is far from being the only simple feature of C that
renders it too complex for you to properly understand. As far as I can tell,
there isn't any feature of C that is too simple for you to misunderstand.
Post by bartc
The problem is that the A[i] notation looks so remarkably like
subscripting that it can be mistaken for scripting.
That's because it is subscripting, as C defines that term. The fact that you insist on using a conflicting concept of what subscripting is, is the key reason you can't understand C's approach.
Post by bartc
There're also the problem that the same A[i] notation is used both when
A starts off as an actual array, as when it starts as a pointer.
No, if A is an array, it gets converted into a pointer to the first element of that array, so it's actually a pointer either way, which is what permits the same notation to be used. Again, your inability to assimilate this simple rule is part of what prevents you from properly understanding C.
Post by bartc
... This
rapidly gets confusing with extra dimensions. The use of pointers
usually requires that some allocation or setup is done, but the fact
that explicit pointers are being used is now hidden.
The use of explicit pointers is not hidden - it's exhibited quite clearly in the declaration for the appropriate identifier, where it belongs.
Post by bartc
Post by j***@verizon.net
I won't claim that everyone programming in C understands these concepts as well
as I do, but most of them find those concepts easier to understand than you do,
or they wouldn't be able to hold a job as a C programmer.
I don't know how C managed to get to its current position (perhaps a
lack of competition among systems programming languages). But it's more
likely to have been in spite of its arcane type system rather than
because of it.
It only seems that way because C's approach conflicts with your own ideas of what a language should look like - it's not because your ideas define actual requirements for usable languages.

Your attitude toward C reminds me of someone who's figured out that three-wheeled vehicles are stable against falling over, even at small speeds, and that four-wheeled vehicles are even more stable. When presented with a motorcycle, you proclaim "That can't work", and insist on attaching training wheels that make it less efficient, less maneuverable, noisier, and have a lower top speed. Then you complain about all of those problems, blaming them on the motorcycle's poor design, rather than attempting to understand that a motorcycle uses a different approach to the problem of remaining upright than is used by a car or truck. It's a more subtle approach, one that doesn't work at excessively low speeds, and it requires some training to make that approach work - but with that training, it does work. That's not a precise analogy to C's features that confuse you, but it accurately reflects the attitude problem that lies behind your confusion.
bartc
2017-04-01 17:49:26 UTC
Permalink
Post by j***@verizon.net
Keep in mind that this is far from being the only simple feature of C that
renders it too complex for you to properly understand. As far as I can tell,
there isn't any feature of C that is too simple for you to misunderstand.
I'm at a disadvantage in that I've been spoilt by too many years of
using a similar low-level language where a lot of this stuff is
implemented properly and more sensibly.

It's like being adapted to using a screwdriver for one purpose (driving
screws) and a chisel for another (um, chiselling I guess), and then
having to work somewhere where one tool tried to do the job of both. You
might well question such a practice.
Post by j***@verizon.net
Post by bartc
There're also the problem that the same A[i] notation is used both when
A starts off as an actual array, as when it starts as a pointer.
No, if A is an array, it gets converted into a pointer to the first element of that array, so it's actually a pointer either way, which is what permits the same notation to be used. Again, your inability to assimilate this simple rule is part of what prevents you from properly understanding C.
This is where you start to pull the wool over people's eyes. The
difference between an A of type T[], and one of type T*, can be very
significant. And the intention of the programmer can also be different,
not only between T[] and T*, but between a T* that points at a single
instance of T and one that can point to N instances.

Three different uses, but all can be used legally, if not all correctly,
with A[i].
Post by j***@verizon.net
Post by bartc
... This
rapidly gets confusing with extra dimensions. The use of pointers
usually requires that some allocation or setup is done, but the fact
that explicit pointers are being used is now hidden.
The use of explicit pointers is not hidden - it's exhibited quite clearly in the declaration for the appropriate identifier, where it belongs.
typedef int T[5];

The declaration of 'A' is 'T A'. The declaration of T may be buried in a
header (and I've been looking at a /lot/ of header code recently; it can
be like a treasure hunt).

What's the actual type of A? What is its size?

If I run this code (where ints are 4 bytes, pointers 8), then sizeof(A)
gives me either 8 or 20, depending on whether 'T A' is in a parameter
list or not. Where's the pointer declaration in the latter case?
--
Bartc
s***@casperkitty.com
2017-04-01 16:35:01 UTC
Permalink
Post by j***@verizon.net
It is an array - of length one. It's an inherent feature (NOT a bug) of C that,
as far as pointer arithmetic is concerned, any single object of a given type
can be treated as the first and only element of a 1-element array of that type.
Unless you're going to forbid 1-element arrays, that's a reasonable approach to
take. It makes it possible to write routines that can handle arbitrary length
arrays, and to pass to such a routine a pointer to a single object, and have it
work correctly without any need to re-write the interface of the routine.
It is a characteristic of the language which helped make it suit the kinds
of programming for which it was designed, and make it less suitable for some
other kinds.

As C was originally designed, objects had addresses, and the state of an
object was encapsulated by the contents of storage at that address. A
compiler didn't need to know nor care where an address came from. While
most languages made a distinction between passing an object by reference,
passing a pointer to a dynamically-allocated object, and passing a pointer
to an array of objects, ignoring such distinctions was easier than enforcing
them. It also allowed programs to do things like iterate over elements of
a structure by treating it as an array.

On the other hand, if a language does make the aforementioned distinctions
a compiler can easily perform many kinds of optimization which are, at best,
much harder in C. In C, given

void bar1(int *p);

int foo(void)
{
int arr1[2],arr2[1];
arr1[1] = 0;
bar(&(arr1[0]));
arr1[1]++;
bar(&(arr2[0]));
return arr1[1];
}

a compiler would have to allow for the possibility that either or both calls
to bar might access arr1[1]. In a language with pass-by-reference semantics
and a distinction between pointers that would be usable as arrays, versus
pointers to distinct elements, that would not be necessary.

Similarly, while it is sometimes useful to be able to pass individual objects
as arrays, it generally prevents compilers from being able to distinguish
accesses to things which are *known* to be arrays, from things which might
not be; an inability to make that distinction would compel a compiler to
allow for possible aliasing between a pointer used to access e.g. an int[],
and structure fields of type "int".

Having "universal" pointer types is useful, but that doesn't mean that all
pointers need to be universal. Having different kinds of pointers with
varying abilities to access different things would allow for more optimiz-
ations without impairing semantic flexibility, and would likely be simpler
than trying to optimize around the lack of such a feature.
s***@casperkitty.com
2017-03-31 23:33:13 UTC
Permalink
Post by s***@gmail.com
What I feel like is that we must banish all other forms except the way an "incomplete array of ..." can be declared as a function parameter to actually mean "pointer to ...". I also feel like we should allow this only in a direct declarator. So for example f(int (a)[]) would be a constraint violation (maybe we could make use of that form in the future?).
The problem is that the treatment of array types differently in parameter
lists versus everywhere else is semantic rather than syntactic, and there
is no way to declare a parameter in a way that says "treat this as a value
even if it's an array", nor is there a way to declare any other variable
that says "use this type if it isn't an array, or else substitute a pointer
to the element type".

Perhaps adding a syntax which attaches [] to a type rather than the object
identified thereby, and indicates that the object in question is an array
of the indicated type, might help with the former issue. I'm not sure if
there's a clean solution for the latter.
s***@gmail.com
2017-04-01 14:58:02 UTC
Permalink
Post by s***@casperkitty.com
Post by s***@gmail.com
What I feel like is that we must banish all other forms except the way an "incomplete array of ..." can be declared as a function parameter to actually mean "pointer to ...". I also feel like we should allow this only in a direct declarator. So for example f(int (a)[]) would be a constraint violation (maybe we could make use of that form in the future?).
The problem is that the treatment of array types differently in parameter
lists versus everywhere else is semantic rather than syntactic, and there
is no way to declare a parameter in a way that says "treat this as a value
even if it's an array", nor is there a way to declare any other variable
that says "use this type if it isn't an array, or else substitute a pointer
to the element type".
Perhaps adding a syntax which attaches [] to a type rather than the object
identified thereby, and indicates that the object in question is an array
of the indicated type, might help with the former issue. I'm not sure if
there's a clean solution for the latter.
I don't exactly understand what you are saying here.
jacob navia
2017-04-03 00:34:40 UTC
Permalink
Post by s***@casperkitty.com
The problem is that the treatment of array types differently in parameter
lists versus everywhere else is semantic rather than syntactic, and there
is no way to declare a parameter in a way that says "treat this as a value
even if it's an array",
typedef struct { int tab{256}; } Array;

void fn(Array a);

Here the array is being passed by value.
bartc
2017-04-03 09:30:22 UTC
Permalink
Post by jacob navia
Post by s***@casperkitty.com
The problem is that the treatment of array types differently in parameter
lists versus everywhere else is semantic rather than syntactic, and there
is no way to declare a parameter in a way that says "treat this as a value
even if it's an array",
typedef struct { int tab{256}; } Array;
void fn(Array a);
Here the array is being passed by value.
Does it? If I try this:

typedef struct { int tab[256]; } Array;

void fn(Array a){
int i;
a[i];
}

I get:

Error d.c: 5 operands of + have illegal types 'Array' and 'int'
Error d.c: 5 type error: pointer expected

While the elements of tab are being passed by value, you can't access
tab directly. The parameter type isn't itself an array.
--
bartc
Francis Glassborow
2017-04-04 09:25:10 UTC
Permalink
Post by bartc
Post by jacob navia
Post by s***@casperkitty.com
The problem is that the treatment of array types differently in parameter
lists versus everywhere else is semantic rather than syntactic, and there
is no way to declare a parameter in a way that says "treat this as a value
even if it's an array",
typedef struct { int tab{256}; } Array;
void fn(Array a);
Here the array is being passed by value.
typedef struct { int tab[256]; } Array;
void fn(Array a){
int i;
at this point i has indeterminate value.
Post by bartc
a[i];
a is NOT an array, it is a struct so the correct syntax is:
a.tab[i];
which has undefined behaviour because you forgot to initialise i.
Post by bartc
}
Error d.c: 5 operands of + have illegal types 'Array' and 'int'
Error d.c: 5 type error: pointer expected
While the elements of tab are being passed by value, you can't access
tab directly. The parameter type isn't itself an array.
So? The array has been passed by value, but you need to use its name
which in this case is a.tab
jacob navia
2017-04-04 22:29:32 UTC
Permalink
Post by Francis Glassborow
Post by bartc
Post by jacob navia
Post by s***@casperkitty.com
The problem is that the treatment of array types differently in parameter
lists versus everywhere else is semantic rather than syntactic, and there
is no way to declare a parameter in a way that says "treat this as a value
even if it's an array",
typedef struct { int tab{256}; } Array;
void fn(Array a);
Here the array is being passed by value.
typedef struct { int tab[256]; } Array;
void fn(Array a){
int i;
at this point i has indeterminate value.
Post by bartc
a[i];
a.tab[i];
which has undefined behaviour because you forgot to initialise i.
Yes, but you see what I wanted to do isn't it?
Post by Francis Glassborow
Post by bartc
}
Error d.c: 5 operands of + have illegal types 'Array' and 'int'
Error d.c: 5 type error: pointer expected
Why you compile that code? You can't just read it with typos included?

We are in usenet, and we try to communicate through written thoughts.
I made a typo, is that serious doctor?

:-)
Post by Francis Glassborow
Post by bartc
While the elements of tab are being passed by value, you can't access
tab directly. The parameter type isn't itself an array.
As I said elsewhere, C array handling with all this "decay" nonsense is
a very bad idea. But there is this work around. All languages have warts
and bugs, here is a work around for this one.
Post by Francis Glassborow
So? The array has been passed by value, but you need to use its name
which in this case is a.tab
Yes.
Keith Thompson
2017-04-04 23:18:21 UTC
Permalink
Post by jacob navia
Post by s***@casperkitty.com
The problem is that the treatment of array types differently in
parameter lists versus everywhere else is semantic rather than
syntactic, and there is no way to declare a parameter in a way
that says "treat this as a value even if it's an array",
typedef struct { int tab[256]; } Array; /* typos corrected -- kst */
void fn(Array a);
Here the array is being passed by value.
[...]
Post by jacob navia
As I said elsewhere, C array handling with all this "decay" nonsense is
a very bad idea. But there is this work around. All languages have warts
and bugs, here is a work around for this one.
Yes, but it's a limited workaround. You have to specify the length of
the array as a constant in the source; you can't use this mechanism to
pass an array of arbitrary size. I suspect (without proof) that *most*
code that manipulates arrays needs to deal with arrays of arbitrary
size. For example, a line of text (represented as an array of
characters) might have a length anywhere from 0 up to some very large
value, and allocating the maximum for each line would be wasteful.

I'm not saying that C's "decay" nonsense is the best possible mechanism,
but it was a decent one back in the 1970s, and it hasn't aged all *that*
badly. It has the advantage of being extremely flexible, and the
disadvantage of making the programmer responsible for a lot of
bookkeeping.
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
David Brown
2017-04-05 08:53:01 UTC
Permalink
Post by Keith Thompson
Post by jacob navia
Post by s***@casperkitty.com
The problem is that the treatment of array types differently in
parameter lists versus everywhere else is semantic rather than
syntactic, and there is no way to declare a parameter in a way
that says "treat this as a value even if it's an array",
typedef struct { int tab[256]; } Array; /* typos corrected -- kst */
void fn(Array a);
Here the array is being passed by value.
[...]
Post by jacob navia
As I said elsewhere, C array handling with all this "decay" nonsense is
a very bad idea. But there is this work around. All languages have warts
and bugs, here is a work around for this one.
Yes, but it's a limited workaround. You have to specify the length of
the array as a constant in the source; you can't use this mechanism to
pass an array of arbitrary size. I suspect (without proof) that *most*
code that manipulates arrays needs to deal with arrays of arbitrary
size. For example, a line of text (represented as an array of
characters) might have a length anywhere from 0 up to some very large
value, and allocating the maximum for each line would be wasteful.
I can't answer for "most" code, but in my line of work - small embedded
systems - array size is almost always fixed at compile time. We avoid
dynamic sizing and dynamic memory where possible (there a few exceptions
- for example, it is hard to avoid dynamic memory when you have
Ethernet). I sometimes have my arrays wrapped inside structs - but it
is not particularly common to need to pass them to functions by value.
However, it means all information about sizes and array limits is
preserved (as part of the type) when passing them as pointers.

When trying to guess about "most code", we can take /evidence/, but not
proof, from the evolution of C++. For a long time, there has been
std::vector<>, which is a type that implements an array of arbitrary
size that is easily resizeable. There is no /unresizeable/ arbitrary
sized array corresponding directly to C arrays, but there is no need for
such a type - just use a std::vector and don't resize it. Recently
(C++11) the type std::array<> was added - this is meant as a safe thin
wrapper around standard C arrays whose size is known at compile time.
The size is then part of the type of the std::array<>.

The fact that std::array<> was added to C++11 suggests that arrays of
compile-time-known size are common enough to warrant having their own
type with more "normal" characteristics, such as passing by value
without decaying to a pointer. Wrapping an array in a struct is the
nearest C has to this.
Post by Keith Thompson
I'm not saying that C's "decay" nonsense is the best possible mechanism,
but it was a decent one back in the 1970s, and it hasn't aged all *that*
badly. It has the advantage of being extremely flexible, and the
disadvantage of making the programmer responsible for a lot of
bookkeeping.
Ben Bacarisse
2017-04-05 10:15:04 UTC
Permalink
Post by David Brown
Post by Keith Thompson
Post by jacob navia
Post by s***@casperkitty.com
The problem is that the treatment of array types differently in
parameter lists versus everywhere else is semantic rather than
syntactic, and there is no way to declare a parameter in a way
that says "treat this as a value even if it's an array",
typedef struct { int tab[256]; } Array; /* typos corrected -- kst */
void fn(Array a);
Here the array is being passed by value.
[...]
Post by jacob navia
As I said elsewhere, C array handling with all this "decay" nonsense is
a very bad idea. But there is this work around. All languages have warts
and bugs, here is a work around for this one.
Yes, but it's a limited workaround. You have to specify the length of
the array as a constant in the source; you can't use this mechanism to
pass an array of arbitrary size. I suspect (without proof) that *most*
code that manipulates arrays needs to deal with arrays of arbitrary
size. For example, a line of text (represented as an array of
characters) might have a length anywhere from 0 up to some very large
value, and allocating the maximum for each line would be wasteful.
I can't answer for "most" code, but in my line of work - small embedded
systems - array size is almost always fixed at compile time. We avoid
dynamic sizing and dynamic memory where possible (there a few exceptions
- for example, it is hard to avoid dynamic memory when you have
Ethernet).
Note that "fixed at compile time" is not the same as "one size fits
all". By putting an array in a struct, a function is limited to
operating on that size alone. A program might have arrays of many
different fixed size, but putting them into structs to emulate
pass-by-value would preclude writing generic array-manipulation
functions.

Obviously I'm only talking about cases where you want the pass-by-value
semantics, but I'm having trouble thinking of use-cases!

<snip>
--
Ben.
David Brown
2017-04-05 11:56:30 UTC
Permalink
Post by Ben Bacarisse
Post by David Brown
Post by Keith Thompson
Post by jacob navia
Post by s***@casperkitty.com
The problem is that the treatment of array types differently in
parameter lists versus everywhere else is semantic rather than
syntactic, and there is no way to declare a parameter in a way
that says "treat this as a value even if it's an array",
typedef struct { int tab[256]; } Array; /* typos corrected -- kst */
void fn(Array a);
Here the array is being passed by value.
[...]
Post by jacob navia
As I said elsewhere, C array handling with all this "decay" nonsense is
a very bad idea. But there is this work around. All languages have warts
and bugs, here is a work around for this one.
Yes, but it's a limited workaround. You have to specify the length of
the array as a constant in the source; you can't use this mechanism to
pass an array of arbitrary size. I suspect (without proof) that *most*
code that manipulates arrays needs to deal with arrays of arbitrary
size. For example, a line of text (represented as an array of
characters) might have a length anywhere from 0 up to some very large
value, and allocating the maximum for each line would be wasteful.
I can't answer for "most" code, but in my line of work - small embedded
systems - array size is almost always fixed at compile time. We avoid
dynamic sizing and dynamic memory where possible (there a few exceptions
- for example, it is hard to avoid dynamic memory when you have
Ethernet).
Note that "fixed at compile time" is not the same as "one size fits
all". By putting an array in a struct, a function is limited to
operating on that size alone. A program might have arrays of many
different fixed size, but putting them into structs to emulate
pass-by-value would preclude writing generic array-manipulation
functions.
Yes. (C++ gets around that with templates.)

My common use-cases for putting arrays in a struct would things like a
buffer of input values, or an array of parameters. A function that
manipulates that kind of thing would not need to be used on different
sized arrays - the function that calculates a filter on the values in
the input buffer could take an input buffer type as a parameter. It
would never need to handle a parameter with a different size.

Clearly there are times when you /do/ want functions that handle
different sized arrays - but often I find my functions deal with only a
single fixed size.
Post by Ben Bacarisse
Obviously I'm only talking about cases where you want the pass-by-value
semantics, but I'm having trouble thinking of use-cases!
I find it useful to have specific types of fixed-size arrays, even
though I rarely pass them by value - you get many benefits when passing
pointers. It means you have concrete knowledge of the array size within
the function. You can use "sizeof" on it without the surprising effects
of applying sizeof to a standard array parameter. And you cannot
accidentally pass the wrong type of data into the function - pointers to
wrapped arrays of different sizes (or even of the same size, but in
different defined structs) is a compile-time error.

(The use of "sizeof" on parameters of standard C array types is so
surprising to many people that when gcc added a
"-Wsizeof-array-argument" warning, they enabled it by default without
needing any other compiler flags.)
s***@casperkitty.com
2017-04-05 19:09:12 UTC
Permalink
Post by jacob navia
As I said elsewhere, C array handling with all this "decay" nonsense is
a very bad idea. But there is this work around. All languages have warts
and bugs, here is a work around for this one.
The ability to implicitly coerce an array to a pointer is a useful feature.
What was not helpful was having array *lvalue types* regarded as synonymous
with pointer *lvalue types* exclusively in function parameters but not in
other contexts.

If the syntax "double(*)[]" referred to a combination of a "double*" and a
"size_t", and if coercing a either a "double[5]" or a "double(*)[5]" would
combine the array's address with the number 5, that have allowed compilers
to provide automatic bounds checking in many more situations than are
presently possible without programmers having to manually pass array bounds.
Code which wants to pass a portion of one array as though it were a smaller
array could do e.g.

double bigArray[1000];
processRange( (double(*smallerArray)[5])(bigArray+9) );

to force the compiler to treat elements 9..14 of the big array as though
they were an array of 5 elements.
Keith Thompson
2017-04-05 20:43:10 UTC
Permalink
Post by s***@casperkitty.com
Post by jacob navia
As I said elsewhere, C array handling with all this "decay" nonsense is
a very bad idea. But there is this work around. All languages have warts
and bugs, here is a work around for this one.
The ability to implicitly coerce an array to a pointer is a useful feature.
What was not helpful was having array *lvalue types* regarded as synonymous
with pointer *lvalue types* exclusively in function parameters but not in
other contexts.
It would be helpful if you'd use terms correctly. I don't know
what an "lvalue type" is, or how it differs from an "object type".
An lvalue is a kind of expression, not another name for "object".

You are referring, I believe, to the rule in 6.7.6.3p7 that says:

A declaration of a parameter as "array of type" shall be adjusted
to "qualified pointer to type", where the type qualifiers
(if any) are those specified within the [ and ] of the array
type derivation.

I don't particularly like that rule myself, but we're stuck with it.
It's even used by the Standard for one of the standard forms of main:

int main(int argc, char *argv[]) { /* ... */ }

I personally prefer to write:

int main(int argc, char **argv) { /* ... */ }

but the square brackets do provide some (unchecked) documentation about
what arguments are expected.
Post by s***@casperkitty.com
If the syntax "double(*)[]" referred to a combination of a "double*" and a
"size_t", and if coercing a either a "double[5]" or a "double(*)[5]" would
combine the array's address with the number 5, that have allowed compilers
to provide automatic bounds checking in many more situations than are
presently possible without programmers having to manually pass array bounds.
Code which wants to pass a portion of one array as though it were a smaller
array could do e.g.
double bigArray[1000];
processRange( (double(*smallerArray)[5])(bigArray+9) );
to force the compiler to treat elements 9..14 of the big array as though
they were an array of 5 elements.
Yes, it would be nice if C had some kind of lightweight syntactic sugar
for passing the base address and length of an array, and perhaps even
being able to apply sizeof to array parameters. I'm not sure what that
should look like. (You can currently do something with VLA parameters,
but I haven't looked into that.)
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
David Brown
2017-04-05 21:24:42 UTC
Permalink
Post by Keith Thompson
Post by s***@casperkitty.com
Post by jacob navia
As I said elsewhere, C array handling with all this "decay" nonsense is
a very bad idea. But there is this work around. All languages have warts
and bugs, here is a work around for this one.
The ability to implicitly coerce an array to a pointer is a useful feature.
What was not helpful was having array *lvalue types* regarded as synonymous
with pointer *lvalue types* exclusively in function parameters but not in
other contexts.
It would be helpful if you'd use terms correctly. I don't know
what an "lvalue type" is, or how it differs from an "object type".
An lvalue is a kind of expression, not another name for "object".
A declaration of a parameter as "array of type" shall be adjusted
to "qualified pointer to type", where the type qualifiers
(if any) are those specified within the [ and ] of the array
type derivation.
I don't particularly like that rule myself, but we're stuck with it.
int main(int argc, char *argv[]) { /* ... */ }
int main(int argc, char **argv) { /* ... */ }
but the square brackets do provide some (unchecked) documentation about
what arguments are expected.
Post by s***@casperkitty.com
If the syntax "double(*)[]" referred to a combination of a "double*" and a
"size_t", and if coercing a either a "double[5]" or a "double(*)[5]" would
combine the array's address with the number 5, that have allowed compilers
to provide automatic bounds checking in many more situations than are
presently possible without programmers having to manually pass array bounds.
Code which wants to pass a portion of one array as though it were a smaller
array could do e.g.
double bigArray[1000];
processRange( (double(*smallerArray)[5])(bigArray+9) );
to force the compiler to treat elements 9..14 of the big array as though
they were an array of 5 elements.
Yes, it would be nice if C had some kind of lightweight syntactic sugar
for passing the base address and length of an array, and perhaps even
being able to apply sizeof to array parameters. I'm not sure what that
should look like. (You can currently do something with VLA parameters,
but I haven't looked into that.)
It could be something as simple as making:

int foo(int arr[10]);

mean that "arr" really is supposed to be an array of size 10, while
still being passed by pointer. "sizeof arr" should evaluate to 10 *
sizeof(int), rather than sizeof(int*). Passing a parameter that is not
an array of size 10 would be undefined behaviour, possibly letting the
compiler optimise somewhat. And declarations of the function would have
to match properly - "int foo(int * arr)" would /not/ be allowed as a match.

Then a sophisticated compiler would be able to do range checking on
accesses to the array.

I can understand why array expressions decay to pointers, and why
passing arrays as parameters actually passes pointers - that's fine.
But I find it very odd that the C standards give a syntax that /looks/
like it lets you use arrays as parameters, when in fact it is nothing
more than a comment.
Keith Thompson
2017-04-05 22:23:25 UTC
Permalink
Post by David Brown
Post by Keith Thompson
Post by s***@casperkitty.com
Post by jacob navia
As I said elsewhere, C array handling with all this "decay" nonsense is
a very bad idea. But there is this work around. All languages have warts
and bugs, here is a work around for this one.
The ability to implicitly coerce an array to a pointer is a useful feature.
What was not helpful was having array *lvalue types* regarded as synonymous
with pointer *lvalue types* exclusively in function parameters but not in
other contexts.
It would be helpful if you'd use terms correctly. I don't know
what an "lvalue type" is, or how it differs from an "object type".
An lvalue is a kind of expression, not another name for "object".
A declaration of a parameter as "array of type" shall be adjusted
to "qualified pointer to type", where the type qualifiers
(if any) are those specified within the [ and ] of the array
type derivation.
I don't particularly like that rule myself, but we're stuck with it.
int main(int argc, char *argv[]) { /* ... */ }
int main(int argc, char **argv) { /* ... */ }
but the square brackets do provide some (unchecked) documentation about
what arguments are expected.
Post by s***@casperkitty.com
If the syntax "double(*)[]" referred to a combination of a "double*" and a
"size_t", and if coercing a either a "double[5]" or a "double(*)[5]" would
combine the array's address with the number 5, that have allowed compilers
to provide automatic bounds checking in many more situations than are
presently possible without programmers having to manually pass array bounds.
Code which wants to pass a portion of one array as though it were a smaller
array could do e.g.
double bigArray[1000];
processRange( (double(*smallerArray)[5])(bigArray+9) );
to force the compiler to treat elements 9..14 of the big array as though
they were an array of 5 elements.
Yes, it would be nice if C had some kind of lightweight syntactic sugar
for passing the base address and length of an array, and perhaps even
being able to apply sizeof to array parameters. I'm not sure what that
should look like. (You can currently do something with VLA parameters,
but I haven't looked into that.)
int foo(int arr[10]);
mean that "arr" really is supposed to be an array of size 10, while
still being passed by pointer.
That's similar to the existing

int foo(int arr[static 10]);

except that the "static" mechanism just means passing a pointer that
doesn't point to the initial element of an array of at least 10 elements
causes undefined beahvior.
Post by David Brown
"sizeof arr" should evaluate to 10 *
sizeof(int), rather than sizeof(int*).
That might have been a good idea several decades ago, but changing it
now would break existing code.
Post by David Brown
Passing a parameter that is not
an array of size 10 would be undefined behaviour, possibly letting the
compiler optimise somewhat. And declarations of the function would have
to match properly - "int foo(int * arr)" would /not/ be allowed as a match.
Then a sophisticated compiler would be able to do range checking on
accesses to the array.
I can understand why array expressions decay to pointers, and why
passing arrays as parameters actually passes pointers - that's fine.
But I find it very odd that the C standards give a syntax that /looks/
like it lets you use arrays as parameters, when in fact it is nothing
more than a comment.
Agreed. But if the language is going to add a mechanism that lets you
define an array parameter that really is an array parameter, so that
`sizeof param` gives you the size of the array object, it's going to
have to use some new syntax.
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
David Brown
2017-04-06 08:08:07 UTC
Permalink
Post by Keith Thompson
Post by David Brown
Post by Keith Thompson
Post by s***@casperkitty.com
Post by jacob navia
As I said elsewhere, C array handling with all this "decay" nonsense is
a very bad idea. But there is this work around. All languages have warts
and bugs, here is a work around for this one.
The ability to implicitly coerce an array to a pointer is a useful feature.
What was not helpful was having array *lvalue types* regarded as synonymous
with pointer *lvalue types* exclusively in function parameters but not in
other contexts.
It would be helpful if you'd use terms correctly. I don't know
what an "lvalue type" is, or how it differs from an "object type".
An lvalue is a kind of expression, not another name for "object".
A declaration of a parameter as "array of type" shall be adjusted
to "qualified pointer to type", where the type qualifiers
(if any) are those specified within the [ and ] of the array
type derivation.
I don't particularly like that rule myself, but we're stuck with it.
int main(int argc, char *argv[]) { /* ... */ }
int main(int argc, char **argv) { /* ... */ }
but the square brackets do provide some (unchecked) documentation about
what arguments are expected.
Post by s***@casperkitty.com
If the syntax "double(*)[]" referred to a combination of a "double*" and a
"size_t", and if coercing a either a "double[5]" or a "double(*)[5]" would
combine the array's address with the number 5, that have allowed compilers
to provide automatic bounds checking in many more situations than are
presently possible without programmers having to manually pass array bounds.
Code which wants to pass a portion of one array as though it were a smaller
array could do e.g.
double bigArray[1000];
processRange( (double(*smallerArray)[5])(bigArray+9) );
to force the compiler to treat elements 9..14 of the big array as though
they were an array of 5 elements.
Yes, it would be nice if C had some kind of lightweight syntactic sugar
for passing the base address and length of an array, and perhaps even
being able to apply sizeof to array parameters. I'm not sure what that
should look like. (You can currently do something with VLA parameters,
but I haven't looked into that.)
int foo(int arr[10]);
mean that "arr" really is supposed to be an array of size 10, while
still being passed by pointer.
That's similar to the existing
int foo(int arr[static 10]);
except that the "static" mechanism just means passing a pointer that
doesn't point to the initial element of an array of at least 10 elements
causes undefined beahvior.
That is true. However, as far as I can see, adding "static" gives
absolutely nothing of use to the programmer, and only an extremely
slight possibility of additional freedom to the compiler. The cost is
additional verbosity in a way that is unfamiliar to many programmers
(and incompatible with C++, for those that care about that). I'd be
surprised to see it in any real-world code.
Post by Keith Thompson
Post by David Brown
"sizeof arr" should evaluate to 10 *
sizeof(int), rather than sizeof(int*).
That might have been a good idea several decades ago, but changing it
now would break existing code.
Agreed. But it would have been nice :-)

I don't think it would break much existing code, however - applying
sizeof to an array parameter is almost certainly a bug in code because
it gives you little useful information while looking like it gives you
the size of the array.
Post by Keith Thompson
Post by David Brown
Passing a parameter that is not
an array of size 10 would be undefined behaviour, possibly letting the
compiler optimise somewhat. And declarations of the function would have
to match properly - "int foo(int * arr)" would /not/ be allowed as a match.
Then a sophisticated compiler would be able to do range checking on
accesses to the array.
I can understand why array expressions decay to pointers, and why
passing arrays as parameters actually passes pointers - that's fine.
But I find it very odd that the C standards give a syntax that /looks/
like it lets you use arrays as parameters, when in fact it is nothing
more than a comment.
Agreed. But if the language is going to add a mechanism that lets you
define an array parameter that really is an array parameter, so that
`sizeof param` gives you the size of the array object, it's going to
have to use some new syntax.
Yes - it's hard to change something that was broken earlier.
Consistency of behaviour is critical to C.

I think the best we could hope for here is that compiler implementations
could have optional warnings that give better checks on array parameters
- then we could use the same existing syntax and be compatible across
compilers, but get the benefits of stronger checking when using
compilers that support it.

gcc already has a "-Wsizeof-array-argument" warning that triggers if you
use sizeof on an array parameter. You can't use sizeof to get the
array's real size - but since you have the size somewhere in the
function prototype, you can get it in other ways. What is now needed is
a warning that a function declaration and definition use different ways
to express pointer arguments that are compatible according to the C
standard, but /should/ be incompatible because they say different things.
Keith Thompson
2017-04-06 16:09:16 UTC
Permalink
[...]
Post by David Brown
Post by Keith Thompson
That's similar to the existing
int foo(int arr[static 10]);
except that the "static" mechanism just means passing a pointer that
doesn't point to the initial element of an array of at least 10 elements
causes undefined beahvior.
That is true. However, as far as I can see, adding "static" gives
absolutely nothing of use to the programmer, and only an extremely
slight possibility of additional freedom to the compiler. The cost is
additional verbosity in a way that is unfamiliar to many programmers
(and incompatible with C++, for those that care about that). I'd be
surprised to see it in any real-world code.
Compilers can warn about potential undefined behavior. gcc doesn't
currently do so in this case, but clang does:

$ cat c.c
#include <stdio.h>
void func(char s[static 10]) { puts(s); }
int main(void) { func("hello"); }
$ clang -c c.c
c.c:3:18: warning: array argument is too small; contains 6 elements,
callee requires at least 10 [-Warray-bounds]
int main(void) { func("hello"); }
^ ~~~~~~~
c.c:2:16: note: callee declares array parameter as static here
void func(char s[static 10]) { puts(s); }
^~~~~~~~~~~~
1 warning generated.
$
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
Jakob Bohm
2017-04-06 17:06:21 UTC
Permalink
Post by Keith Thompson
[...]
Post by David Brown
Post by Keith Thompson
That's similar to the existing
int foo(int arr[static 10]);
except that the "static" mechanism just means passing a pointer that
doesn't point to the initial element of an array of at least 10 elements
causes undefined beahvior.
That is true. However, as far as I can see, adding "static" gives
absolutely nothing of use to the programmer, and only an extremely
slight possibility of additional freedom to the compiler. The cost is
additional verbosity in a way that is unfamiliar to many programmers
(and incompatible with C++, for those that care about that). I'd be
surprised to see it in any real-world code.
Compilers can warn about potential undefined behavior. gcc doesn't
$ cat c.c
#include <stdio.h>
void func(char s[static 10]) { puts(s); }
int main(void) { func("hello"); }
$ clang -c c.c
c.c:3:18: warning: array argument is too small; contains 6 elements,
callee requires at least 10 [-Warray-bounds]
int main(void) { func("hello"); }
^ ~~~~~~~
c.c:2:16: note: callee declares array parameter as static here
void func(char s[static 10]) { puts(s); }
^~~~~~~~~~~~
1 warning generated.
$
I believe the suggestion was that quality implementations could/should
warn about defined but dubious behavior, where a clearer defined syntax
exists for that.

The classic example is:

"Assignment use as conditional, did you mean '=='"

The ones suggested here would be:

"Different prototypes for %s() disagree on specified array length in
arg %u"
"Definition and prototype for %s() disagree on specified array length
in arg %u"
"Function %s() called with arg %s less than the %u elements declared"
"Function %a() accesses arg %s beyond the %u elements declared"

Where all of these apply to prototypes such as:

int foo(int x[10]);

In other words, without changing the language definition (or its
standard), it would be prudent for implementations to do consistency
checks on any specified array argument lengths, but report the
conformant but inconsistent cases as warnings.

This is preferable to requiring programmers to provide this data via
vendor extensions (Such as MS PREfast annotations) where the language
syntax already provides the useful data.

Similarly, a quality implementation could use an internal vendor
feature to make the common countof() extension in stdlib.h also provide
the expected value for array arguments with a specified length, even
though that macro/intrinsic is traditionally indeterminate/undefined
for such (Without changing the well-defined historic sizeof()
semantics).



Enjoy

Jakob
--
Jakob Bohm, CIO, Partner, WiseMo A/S. https://www.wisemo.com
Transformervej 29, 2860 Søborg, Denmark. Direct +45 31 13 16 10
This public discussion message is non-binding and may contain errors.
WiseMo - Remote Service Management for PCs, Phones and Embedded
Keith Thompson
2017-04-06 19:02:15 UTC
Permalink
Post by Jakob Bohm
Post by Keith Thompson
[...]
Post by David Brown
Post by Keith Thompson
That's similar to the existing
int foo(int arr[static 10]);
except that the "static" mechanism just means passing a pointer that
doesn't point to the initial element of an array of at least 10 elements
causes undefined beahvior.
That is true. However, as far as I can see, adding "static" gives
absolutely nothing of use to the programmer, and only an extremely
slight possibility of additional freedom to the compiler. The cost is
additional verbosity in a way that is unfamiliar to many programmers
(and incompatible with C++, for those that care about that). I'd be
surprised to see it in any real-world code.
Compilers can warn about potential undefined behavior. gcc doesn't
$ cat c.c
#include <stdio.h>
void func(char s[static 10]) { puts(s); }
int main(void) { func("hello"); }
$ clang -c c.c
c.c:3:18: warning: array argument is too small; contains 6 elements,
callee requires at least 10 [-Warray-bounds]
int main(void) { func("hello"); }
^ ~~~~~~~
c.c:2:16: note: callee declares array parameter as static here
void func(char s[static 10]) { puts(s); }
^~~~~~~~~~~~
1 warning generated.
$
I believe the suggestion was that quality implementations could/should
warn about defined but dubious behavior, where a clearer defined syntax
exists for that.
"Assignment use as conditional, did you mean '=='"
"Different prototypes for %s() disagree on specified array length in
arg %u"
"Definition and prototype for %s() disagree on specified array length
in arg %u"
"Function %s() called with arg %s less than the %u elements declared"
"Function %a() accesses arg %s beyond the %u elements declared"
int foo(int x[10]);
Since the 10 in that context has no meaning as far as the language is
concerned, I'd prefer a warning on the declaration itself suggesting
that the 10 should either be omitted or preceded by "static".

If you want a compiler warning when the argument points to the first
element of an array that isn't long enough, you should use the language
mechanism provided for essentially that purpose:

int foo(int x[static 10]);

I find the syntax one of the uglier things in C, but it's standard.

I wouldn't mind a future standard making the useless `10` obsolescent
and/or deprecated.

On the other hand, that might make some code awkward. You might have,
say:

typedef int point[2];
void plot(point p);

That kind of usage can lead to confusion if the programmer doesn't pay
attention to the fact that the point is passed as a pointer, but banning
the useless dimension could break some real code. (Wrapping the array
in a struct would probably result in cleaner code.)

[...]
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
Jakob Bohm
2017-04-07 05:05:57 UTC
Permalink
Post by Keith Thompson
Post by Jakob Bohm
Post by Keith Thompson
[...]
Post by David Brown
Post by Keith Thompson
That's similar to the existing
int foo(int arr[static 10]);
except that the "static" mechanism just means passing a pointer that
doesn't point to the initial element of an array of at least 10 elements
causes undefined beahvior.
That is true. However, as far as I can see, adding "static" gives
absolutely nothing of use to the programmer, and only an extremely
slight possibility of additional freedom to the compiler. The cost is
additional verbosity in a way that is unfamiliar to many programmers
(and incompatible with C++, for those that care about that). I'd be
surprised to see it in any real-world code.
Compilers can warn about potential undefined behavior. gcc doesn't
$ cat c.c
#include <stdio.h>
void func(char s[static 10]) { puts(s); }
int main(void) { func("hello"); }
$ clang -c c.c
c.c:3:18: warning: array argument is too small; contains 6 elements,
callee requires at least 10 [-Warray-bounds]
int main(void) { func("hello"); }
^ ~~~~~~~
c.c:2:16: note: callee declares array parameter as static here
void func(char s[static 10]) { puts(s); }
^~~~~~~~~~~~
1 warning generated.
$
I believe the suggestion was that quality implementations could/should
warn about defined but dubious behavior, where a clearer defined syntax
exists for that.
"Assignment use as conditional, did you mean '=='"
"Different prototypes for %s() disagree on specified array length in
arg %u"
"Definition and prototype for %s() disagree on specified array length
in arg %u"
"Function %s() called with arg %s less than the %u elements declared"
"Function %a() accesses arg %s beyond the %u elements declared"
int foo(int x[10]);
Since the 10 in that context has no meaning as far as the language is
concerned, I'd prefer a warning on the declaration itself suggesting
that the 10 should either be omitted or preceded by "static".
If you want a compiler warning when the argument points to the first
element of an array that isn't long enough, you should use the language
int foo(int x[static 10]);
I find the syntax one of the uglier things in C, but it's standard.
I wouldn't mind a future standard making the useless `10` obsolescent
and/or deprecated.
On the other hand, that might make some code awkward. You might have,
typedef int point[2];
void plot(point p);
That kind of usage can lead to confusion if the programmer doesn't pay
attention to the fact that the point is passed as a pointer, but banning
the useless dimension could break some real code. (Wrapping the array
in a struct would probably result in cleaner code.)
[...]
As others pointed out by others, the [static 10] syntax variant is
incompatible with C++. It is also incompatible with older C compilers.

My suggestion is 100% backward compatible, and basically a lint-like
check that C implementations can add cheaply without harm, as it only
emits warnings, and cleaning up those warnings improves code
readability without changing formal semantics (and thus without
triggering overzealous optimizations).

It is also within the existing standard as it doesn't change anything
the standard officially cares about, only the set of diagnostic
messages a compiler would emit.


Enjoy

Jakob
--
Jakob Bohm, CIO, Partner, WiseMo A/S. https://www.wisemo.com
Transformervej 29, 2860 Søborg, Denmark. Direct +45 31 13 16 10
This public discussion message is non-binding and may contain errors.
WiseMo - Remote Service Management for PCs, Phones and Embedded
s***@casperkitty.com
2017-04-06 17:24:33 UTC
Permalink
Post by David Brown
Post by Keith Thompson
That's similar to the existing
int foo(int arr[static 10]);
except that the "static" mechanism just means passing a pointer that
doesn't point to the initial element of an array of at least 10 elements
causes undefined beahvior.
That is true. However, as far as I can see, adding "static" gives
absolutely nothing of use to the programmer, and only an extremely
slight possibility of additional freedom to the compiler. The cost is
additional verbosity in a way that is unfamiliar to many programmers
(and incompatible with C++, for those that care about that). I'd be
surprised to see it in any real-world code.
It can be very useful for compilers to know that accesses to a[i] and b[j]
will be independent for all i!=j. C has a means of telling the compiler
that they will be independent for all values of i and j, but no form that
would require the compiler recognize aliasing between a[i] and b[i], but
not any other elements of b.
Post by David Brown
Post by Keith Thompson
Post by David Brown
"sizeof arr" should evaluate to 10 *
sizeof(int), rather than sizeof(int*).
That might have been a good idea several decades ago, but changing it
now would break existing code.
If the "array" argument is defined using present syntax, there's not much
that can be done. On the other hand, it might be practical to add some
new syntactical forms using keywords in new ways, which could then have
semantics that are designed to work well together without having to worry
about compatibility with anything, since no existing code would use the
syntax in question.

For example, one could specify that an object a type "typename(auto*)[10]"
would be like a "typename(*)[10]" except that a "*" would automatically be
applied to most usages making it behave like a "typeName[10]"; applying one
"&" operator would undo the default "*" (yielding an object of type
"typename(*)[10]", which may be able to have its address taken in turn.

Additionally, "typename (auto*)[auto]" and "typename(*)[auto] could be a
special kind of pointers which combines a "typename(auto*)[]" with a size_t,
thus allowing sizeof to be used on what would otherwise be an incomplete
type [perhaps allow implementations to decide whether the size should be
stored as an element count or a byte count; implementations that store a
byte count might be more efficient if code uses something like "memcpy"
with the size value, but storing an element count may be more efficient
when evaluating "sizeof foo / sizeof foo[0]".
bartc
2017-04-07 14:44:14 UTC
Permalink
Post by s***@casperkitty.com
For example, one could specify that an object a type "typename(auto*)[10]"
would be like a "typename(*)[10]" except that a "*" would automatically be
applied to most usages making it behave like a "typeName[10]"; applying one
"&" operator would undo the default "*" (yielding an object of type
"typename(*)[10]", which may be able to have its address taken in turn.
Are you talking about true reference parameters? If not, the above
sounds incredibly confusing, especially now that * can appear in both
(*) and [*].

In my parallel language (I think others too) a parameter is made a
reference one by adding '&'.

Then, any uses of the parameter within the function have an automatic
dereference op added. And any arguments in a call to the function have
an automatic & address-of op added. Example using C:

void fn1(int &A[10]) { // A has type ref to array 10 of int
A[i];
}

int B[10];

fn1(B);

Here's the equivalent code without using &:

void fn2(int (*A)[10]) { // A has type ptr to array 10 of int
(*A)[i];
}

int B[10];

fn2(&B);


The version using the & reference param doesn't look that different to
how this is normally done in C:

void fn3(int *A[10]) { // A has type ptr to int
A[i];
}

int B[10];

fn3(B);


But here in fn3, a pointer to int is passed; any array information is
lost. The A[i] access /looks/ like it's indexing an array, but it's not.

In the fn1 example using &, a pointer to an actual array is passed.
sizeof(A) will give the array size. (In this case with a specific
constant length; I don't know if [*] is workable here.)

It's superior to fn2 (and in fact would make it possible to deprecate
that style of 'array' handling, allowing arrays and pointers to be more
usefully segregated).
--
Bartc
s***@casperkitty.com
2017-04-07 17:07:37 UTC
Permalink
Post by bartc
Post by s***@casperkitty.com
For example, one could specify that an object a type "typename(auto*)[10]"
would be like a "typename(*)[10]" except that a "*" would automatically be
applied to most usages making it behave like a "typeName[10]"; applying one
"&" operator would undo the default "*" (yielding an object of type
"typename(*)[10]", which may be able to have its address taken in turn.
Are you talking about true reference parameters? If not, the above
sounds incredibly confusing, especially now that * can appear in both
(*) and [*].
If C had used a postfix operator for pointer dereferencing like Pascal, the
logical way to access elements of an array which was passed as a pointer
would be: thePtr^[i] = 23; which is clean and easy to read. The use of a
prefix operator for dereferencing would make the C equivalent (*thePtr)[i]
which is rather icky looking for what should be a common construct. The goal
is to have the concept that "thePtr is a pointer to an array of known size"
but without having to use the horrid syntax to access elements thereof.
Post by bartc
In my parallel language (I think others too) a parameter is made a
reference one by adding '&'.
I think that's pretty much what's happening in my concept, albeit with
different syntax; I think your approach is probably better.
Post by bartc
It's superior to fn2 (and in fact would make it possible to deprecate
that style of 'array' handling, allowing arrays and pointers to be more
usefully segregated).
Do you see any nice way to integrate that with the concept of a type which
pairs an array's address and size? Having `int[auto]` be an incomplete type
but having a pointer to that type pair a pointer and a size seems a bit
klunky, but probably not too bad except that there should also be a type for
length-denominated void*.

If C made the universal pointer type a pointer to a "memory byte", then a
length-denominated pointer to anything need not be different from any other
pointer to a length-denominated array, and it would have avoided the need
to have code cast between "void*" and "unsigned char*" all the time.
bartc
2017-04-07 19:04:45 UTC
Permalink
Post by s***@casperkitty.com
Post by bartc
Are you talking about true reference parameters? If not, the above
sounds incredibly confusing, especially now that * can appear in both
(*) and [*].
If C had used a postfix operator for pointer dereferencing like Pascal, the
logical way to access elements of an array which was passed as a pointer
would be: thePtr^[i] = 23; which is clean and easy to read. The use of a
prefix operator for dereferencing would make the C equivalent (*thePtr)[i]
which is rather icky looking for what should be a common construct.
Yes, that's the syntax I use in my language. Plus, when reference
parameters are used, the ^ disappears. The result is clean-looking code,
without having to compromise the type system by turning T[] or T(*)[]
types into T* ones, with the loss of safety and information that entails.
Post by s***@casperkitty.com
Post by bartc
It's superior to fn2 (and in fact would make it possible to deprecate
that style of 'array' handling, allowing arrays and pointers to be more
usefully segregated).
Do you see any nice way to integrate that with the concept of a type which
pairs an array's address and size? Having `int[auto]` be an incomplete type
but having a pointer to that type pair a pointer and a size seems a bit
klunky, but probably not too bad except that there should also be a type for
length-denominated void*.
Would modern C's way of passing array dimensions via an associated
parameter N work with a T(*)[N] type? I don't really understand it, and
it seems the top-most (left-most) dimension is ignored anyway.

As for a dedicated type, I've toyed with a Slice type that consists of a
(pointer, length) pair, but nothing came of it. But it wouldn't handle
flexible arrays which is also a requirement -- ie growing an array by
appending elements. That needs at least (pointer, length, allocated).
These ideas tend to rapidly start to become over-ambitious and push the
language towards C++ or Ada.

(I have a dynamic language that does all this properly so have less need
in the lower-level language.)
--
bartc
s***@casperkitty.com
2017-04-10 22:58:36 UTC
Permalink
Post by bartc
Post by s***@casperkitty.com
Do you see any nice way to integrate that with the concept of a type which
pairs an array's address and size? Having `int[auto]` be an incomplete type
but having a pointer to that type pair a pointer and a size seems a bit
klunky, but probably not too bad except that there should also be a type for
length-denominated void*.
Would modern C's way of passing array dimensions via an associated
parameter N work with a T(*)[N] type? I don't really understand it, and
it seems the top-most (left-most) dimension is ignored anyway.
I don't like the "modern C" design. It requires that code go against the
common pre-existing convention which places lengths after pointers, and
is also broken from a type-system perspective. Given:

int foo(int rows, int columns, float data[rows][columns]);

could would be undefined if the function indexes the array pointer as passed
(without casting to some other type), and the caller passes anything other
than a "float(*)[][columns]". Since the size of the array in the calling
code would have to be known by the compiler (either constant or held in a
hidden variable associated with a VLA dimension), why make the caller
explicitly pass a "columns" argument which is required to duplicate something
that compiler already knows?
Post by bartc
As for a dedicated type, I've toyed with a Slice type that consists of a
(pointer, length) pair, but nothing came of it. But it wouldn't handle
flexible arrays which is also a requirement -- ie growing an array by
appending elements. That needs at least (pointer, length, allocated).
These ideas tend to rapidly start to become over-ambitious and push the
language towards C++ or Ada.
An expandable array section could be implemented as a structure which
contains a slice and pointer to an allocation-control function. It may
be useful to standardize a minimal feature set for such a function, but
functions shouldn't be designed to require the direct use of realloc()
or any such methods. If a function will need the ability to extend a
supplied array, it should make use of an allocation method controlled
by the code which supplied the array.
bartc
2017-04-11 12:13:29 UTC
Permalink
Post by s***@casperkitty.com
Post by bartc
But it wouldn't handle
flexible arrays which is also a requirement -- ie growing an array by
appending elements. That needs at least (pointer, length, allocated).
These ideas tend to rapidly start to become over-ambitious and push the
language towards C++ or Ada.
An expandable array section could be implemented as a structure which
contains a slice and pointer to an allocation-control function.
You don't want to pass such an array by value. (It's inefficient, but
you can also end up with multiple copies of the same descriptor, updated
independently.)

But passing by pointer to means you need to have a pointer to a pointer
to the data. And it still doesn't stop anyone making a copy (the same
thing happens with copying a pointer to allocated memory, but here the
pointers are going to be hidden).

However, think of the complications when such a flexible array is part
of a struct, an element of another array, or an element of another
flexible array! You may need throw reference counting in there. Or
garbage collection. And ...

That's why I said it rapidly gets complicated. In the case of C, you
need to keep it simple.
--
bartc
s***@casperkitty.com
2017-04-11 19:33:43 UTC
Permalink
Post by bartc
Post by s***@casperkitty.com
An expandable array section could be implemented as a structure which
contains a slice and pointer to an allocation-control function.
You don't want to pass such an array by value. (It's inefficient, but
you can also end up with multiple copies of the same descriptor, updated
independently.)
I was expecting that code would pass around pointers to expandable array
descriptor to anything that might need to resize them, since copying them
by value would be disastrous. A function which will only read data from a
segment and won't need to resize it or access the object after it returns
could accept a segment by value, however.
Post by bartc
But passing by pointer to means you need to have a pointer to a pointer
to the data. And it still doesn't stop anyone making a copy (the same
thing happens with copying a pointer to allocated memory, but here the
pointers are going to be hidden).
The paradigm would be that a function which receives an object descriptor
would be expected to call the supplied function if it is going to do
anything other than read from the indicated array segment and not persist
any pointer to it after it returns or calls code that might use the object
in any other way.
Post by bartc
However, think of the complications when such a flexible array is part
of a struct, an element of another array, or an element of another
flexible array! You may need throw reference counting in there. Or
garbage collection. And ...
Such things would be the responsibility of the supplied callback function.
Post by bartc
That's why I said it rapidly gets complicated. In the case of C, you
need to keep it simple.
From a language standpoint, I see large value to an array-segment concept,
but see far less value to an "expandable object" type unless it supports
configurable semantics.
b***@gmail.com
2017-04-12 21:57:55 UTC
Permalink
Post by s***@casperkitty.com
From a language standpoint, I see large value to an array-segment concept,
but see far less value to an "expandable object" type unless it supports
configurable semantics.
Yes, I also think sticking to the idea of a 'slice' which gives a 'view' of an array, would keep things reasonably simple, is not that technically difficult and would be a feature in keeping with the spirit of a language such as C.

Although designing a suitable type and specifying how it will interact with existing arrays and pointers - especially /C/ arrays and pointers - wouldn't be trivial.

(Not that it would ever be a serious proposal, but would be interesting to see how it could work.)
s***@casperkitty.com
2017-04-12 23:29:04 UTC
Permalink
Post by b***@gmail.com
Yes, I also think sticking to the idea of a 'slice' which gives a 'view' of an array, would keep things reasonably simple, is not that technically difficult and would be a feature in keeping with the spirit of a language such as C.
Although designing a suitable type and specifying how it will interact with existing arrays and pointers - especially /C/ arrays and pointers - wouldn't be trivial.
(Not that it would ever be a serious proposal, but would be interesting to see how it could work.)
First of all, I'd suggest adding a mem_t, and saying that a mem_t* would
allow the same implicit conversions as a void*, and would be type-, format-,
and alias-compatible with void*, but mem_t would have a size of 1, and the
indirection operator could be applied to mem_t*, yielding an lvalue of type
mem_t. Slices would not be able to use void* (since "void" has no defined
size) but could use "mem_t*".

Secondly, there would be a family of slice types--one for each positive
integer N, which would represent pointers to arrays with one variable
dimension, two variable dimensions, etc. with all variable dimensions at
the left (so an "int [auto][3][auto]" would need to be a 3-dimensional slice
whose second value would always be stored as 3). Slice types would behave
as array types with the corresponding number of dimensions, and array types
would implicitly convert to slice types with the matching number of
dimensions, loading the dimension fields appropriately.

There should probably be a syntactic distinction between casts with the
meaning:

1. I need to interpret this slice as an array of some shape, even though
it isn't known elsewhere as an array of that shape.

2. I need to interpret this slice an array of some shape, and am so
confident that it actually an array of that shape that I don't care
what would happen if it weren't.

3. I need to interpret this slice an array of some shape; it should be
such an array, but I'd like an implementation to check to ensure that
it is and trap if it isn't.

The three kinds of cast are useful in different situations, but C doesn't
distinguish among them.

Ben Bacarisse
2017-04-07 19:14:48 UTC
Permalink
bartc <***@freeuk.com> writes:
<snip>
Post by bartc
In my parallel language (I think others too) a parameter is made a
reference one by adding '&'.
Then, any uses of the parameter within the function have an automatic
dereference op added. And any arguments in a call to the function have
void fn1(int &A[10]) { // A has type ref to array 10 of int
A[i];
}
That would be a strange in C because, currently, postfix type modifiers
have higher precedence that prefix ones. Obviously you can decree that
& and * behave differently but it would be unexpected by many and
inconsistent with C++.

<snip>
Post by bartc
The version using the & reference param doesn't look that different to
void fn3(int *A[10]) { // A has type ptr to int
A[i];
}
I can't find a value for "this" that makes your statement true (and the
comment is wrong).
Post by bartc
int B[10];
fn3(B);
This is a constraint violation.
Post by bartc
But here in fn3, a pointer to int is passed;
Yes, but fn3 expects a pointer to a pointer to int

<snip>
--
Ben.
bartc
2017-04-07 19:21:28 UTC
Permalink
Post by Ben Bacarisse
Post by bartc
The version using the & reference param doesn't look that different to
void fn3(int *A[10]) { // A has type ptr to int
A[i];
}
I can't find a value for "this" that makes your statement true (and the
comment is wrong).
Yes, the parameter should be either int *A or int A[10], not a
combination of both! I believe the '10' is ignored.
--
bartc
Ben Bacarisse
2017-04-07 20:35:18 UTC
Permalink
Post by bartc
Post by Ben Bacarisse
Post by bartc
The version using the & reference param doesn't look that different to
void fn3(int *A[10]) { // A has type ptr to int
A[i];
}
I can't find a value for "this" that makes your statement true (and the
comment is wrong).
Yes, the parameter should be either int *A or int A[10], not a
combination of both! I believe the '10' is ignored.
Given the code that your "how this is normally done in C" refers to, I'd
say a combination of both * and [10] is exactly what the parameter
should be. But then using a pointer to simulate passing fixed-size
arrays by reference is not normally done at all, so maybe this is all a
distraction.
--
Ben.
bartc
2017-04-07 20:46:23 UTC
Permalink
Post by Ben Bacarisse
Post by bartc
Post by Ben Bacarisse
Post by bartc
The version using the & reference param doesn't look that different to
void fn3(int *A[10]) { // A has type ptr to int
A[i];
}
I can't find a value for "this" that makes your statement true (and the
comment is wrong).
Yes, the parameter should be either int *A or int A[10], not a
combination of both! I believe the '10' is ignored.
Given the code that your "how this is normally done in C" refers to, I'd
say a combination of both * and [10] is exactly what the parameter
should be. But then using a pointer to simulate passing fixed-size
arrays by reference is not normally done at all, so maybe this is all a
distraction.
I mean how arrays are normally passed in C. So that an array T[] might
be passed as a T* type, losing its array identity.

Passing a T[] array as a T(*)[] pointer is better, but with more
cluttered syntax for the type, the argument, and for when then the array
is accessed.

I pointed out using references to get the best of both methods:
retaining array identity, and keeping the simpler syntax.
--
bartc
bartc
2017-04-07 10:50:35 UTC
Permalink
Post by David Brown
int foo(int arr[10]);
mean that "arr" really is supposed to be an array of size 10, while
still being passed by pointer. "sizeof arr" should evaluate to 10 *
sizeof(int), rather than sizeof(int*).
The following does that:

void fn(int (*A)[10]){
printf("sizeof(*A) = %d\n",sizeof(*A));
}

int main(void) {
int x[10];

fn(&x);
}

fn() prints 40, for the size of *A (A is a pointer, *A is the array).

And if x was length 11 rather than 10, then it would give a warning.
--
Bartc
Keith Thompson
2017-04-07 15:56:57 UTC
Permalink
Post by bartc
Post by David Brown
int foo(int arr[10]);
mean that "arr" really is supposed to be an array of size 10, while
still being passed by pointer. "sizeof arr" should evaluate to 10 *
sizeof(int), rather than sizeof(int*).
void fn(int (*A)[10]){
printf("sizeof(*A) = %d\n",sizeof(*A));
printf("sizeof(*A) = %zu\n",sizeof(*A));
Post by bartc
}
int main(void) {
int x[10];
fn(&x);
}
fn() prints 40, for the size of *A (A is a pointer, *A is the array).
And if x was length 11 rather than 10, then it would give a warning.
If x is of length 11, the call is a constraint violation. I would
*hope* that the compiler would reject it rather than merely warning
about it, but both gcc and clang warn by default.

In any case, that's a problem if you want fn to be able to handle
arrays of sizes other than 10 (and perfectly fine if you don't).
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
bartc
2017-04-07 19:18:19 UTC
Permalink
Post by Keith Thompson
Post by bartc
printf("sizeof(*A) = %d\n",sizeof(*A));
printf("sizeof(*A) = %zu\n",sizeof(*A));
Yes, you keep pointing this out, and I keep ignoring it.

I ignore it because I KNOW a size is practically never going to be more
than 2000000000 bytes in length. But also because, if I use "%zu", then
gcc, Tiny C and MSVC2008 will just print "zu" instead of the size.

Maybe there are ways of persuading those compilers and/or libraries to
work properly, but it's a lot easier to just do "%d" especially for a
throwaway code fragment.

(For a 64-bit machine, the top half of a 64-bit size is ignored; so it
will just show the wrong result, or a negative one, if a size if bigger
than 2**31. It will not impinge on the next parameter.

For a 32-bit machine, then sizeof values will be 32-bit too (I think 31
bit, but if unsigned 32-bit, then %u is still easier than %zu and better
supported).)
--
bartc
Keith Thompson
2017-04-07 20:15:18 UTC
Permalink
Post by bartc
Post by Keith Thompson
Post by bartc
printf("sizeof(*A) = %d\n",sizeof(*A));
printf("sizeof(*A) = %zu\n",sizeof(*A));
Yes, you keep pointing this out, and I keep ignoring it.
I ignore it because I KNOW a size is practically never going to be more
than 2000000000 bytes in length. But also because, if I use "%zu", then
gcc, Tiny C and MSVC2008 will just print "zu" instead of the size.
It's the runtime library, not the compiler. (I suppose the library is
part of MSVC2008, but gcc and Tiny C are just compilers which do not
implement printf, they just generate code to call it.)

But it can fail if size_t and int are not the same size, even if
the value is in range. (Even then it might "work" depending on
the system's argument passing conventions and byte ordering.)

If you can't count on "%zu" working (i.e., you can't count on your
library conforming to C99 or later), then I suggest casting the value to
the expected type:
printf("sizeof(*A) = %u\n", (unsigned)sizeof(*A));
if you're willing to assume the size won't exceed UINT_MAX bytes, or
printf("sizeof(*A) = %lu\n", (unsigned long)sizeof(*A));
if you aren't.
Post by bartc
Maybe there are ways of persuading those compilers and/or libraries to
work properly, but it's a lot easier to just do "%d" especially for a
throwaway code fragment.
(For a 64-bit machine, the top half of a 64-bit size is ignored; so it
will just show the wrong result, or a negative one, if a size if bigger
than 2**31. It will not impinge on the next parameter.
For a 32-bit machine, then sizeof values will be 32-bit too (I think 31
bit, but if unsigned 32-bit, then %u is still easier than %zu and better
supported).)
Or you can use a cast and not have to think about how 32-bit and 64-bit
arguments are passed.
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
Wojtek Lerch
2017-04-08 12:16:43 UTC
Permalink
Post by bartc
I ignore it because I KNOW a size is practically never going to be more
than 2000000000 bytes in length. But also because, if I use "%zu", then
gcc, Tiny C and MSVC2008 will just print "zu" instead of the size.
My gcc warns when I try to use %d with a size_t. It sound like yours
either is old or has the warning disabled by whoever bundled it with a
library that doesn't support %zu.

$ gcc foo.c
foo.c: In function ‘main’:
foo.c:3:3: warning: format ‘%d’ expects argument of type ‘int’, but
argument 2 has type ‘long unsigned int’ [-Wformat=]
printf( "%d\n", sizeof(int) );
^
bartc
2017-04-08 12:51:20 UTC
Permalink
Post by Wojtek Lerch
Post by bartc
I ignore it because I KNOW a size is practically never going to be more
than 2000000000 bytes in length. But also because, if I use "%zu", then
gcc, Tiny C and MSVC2008 will just print "zu" instead of the size.
My gcc warns when I try to use %d with a size_t. It sound like yours
either is old or has the warning disabled by whoever bundled it with a
library that doesn't support %zu.
$ gcc foo.c
foo.c:3:3: warning: format ‘%d’ expects argument of type ‘int’, but
argument 2 has type ‘long unsigned int’ [-Wformat=]
printf( "%d\n", sizeof(int) );
^
Obviously my gcc is different:

c:\c>type c.c
#include <stdio.h>

int main(void) {
printf("%d\n",sizeof(int));
}

c:\c>gcc c.c -oc.exe

c:\c>c
4

If I try "%zu" as suggested:

c:\c>type c.c
#include <stdio.h>

int main(void) {
printf("%zu\n",sizeof(int));
}

c:\c>gcc c.c -oc.exe

c:\c>c
zu

Same with some other compilers. Or even from a script language (no
sizeof but all integers are 64 bits) which imports printf from msvcrt.dll:

printf("%zu\n",1234)

Output:

zu

So it's a library issue. Which I'm stuck with. I've no idea how to get a
compiler to invoke a different library. And that won't help when I use
printf outside of C, from one of my languages.

But it's hardly a major concern. size_t is going to be unsigned 32 bits
or unsigned 64 bits; it doesn't need a bolted-on language feature just
this one type which is not even a proper type (just an alias for one of
the int types).
--
bartc
bartc
2017-04-07 10:35:12 UTC
Permalink
Post by jacob navia
Post by bartc
typedef struct { int tab[256]; } Array;
Error d.c: 5 operands of + have illegal types 'Array' and 'int'
Error d.c: 5 type error: pointer expected
Why you compile that code? You can't just read it with typos included?
Which code? I compiled my version of your example without the typo. I
just wanted to make sure attempts to access a[i] were actually illegal
and there weren't any new C11 features that would make it possible.
Post by jacob navia
We are in usenet, and we try to communicate through written thoughts.
I made a typo, is that serious doctor?
No, but the errors (from lccwin) were to do with my a[i] access, not
your {256} typo.
--
bartc
Hans-Bernhard Bröker
2017-04-03 21:47:18 UTC
Permalink
Post by jacob navia
typedef struct { int tab{256}; } Array;
void fn(Array a);
Here the array is being passed by value.
No, it's not. That's a _struct_ being passed by value.
Kaz Kylheku
2017-04-03 22:00:14 UTC
Permalink
Post by Hans-Bernhard Bröker
Post by jacob navia
typedef struct { int tab{256}; } Array;
void fn(Array a);
Here the array is being passed by value.
No, it's not. That's a _struct_ being passed by value.
This is a special case of pass by value called "pass by synecdoche". :)
jacob navia
2017-04-03 23:27:56 UTC
Permalink
Post by Hans-Bernhard Bröker
Post by jacob navia
typedef struct { int tab{256}; } Array;
void fn(Array a);
Here the array is being passed by value.
No, it's not. That's a _struct_ being passed by value.
So what?

The same data is passed as a copy of the array. That's what I want when
I do that. And I can forget abaout the completely screwed up C array model.

Every language has it warts.
jacob navia
2017-04-04 22:30:56 UTC
Permalink
Post by Hans-Bernhard Bröker
Post by jacob navia
typedef struct { int tab{256}; } Array;
void fn(Array a);
Here the array is being passed by value.
No, it's not. That's a _struct_ being passed by value.
But the data (the important thing in all this) is being passed by value,
i.e. you give a copy of the data to the called procedure.

The rest is syntax and a bad idea taken to the extreme: arrays "decay"...
Richard Bos
2017-04-05 11:21:43 UTC
Permalink
Post by Hans-Bernhard Bröker
Post by jacob navia
typedef struct { int tab{256}; } Array;
void fn(Array a);
Here the array is being passed by value.
No, it's not. That's a _struct_ being passed by value.
That's not entirely fair. Inside that struct, the array is _also_ passed
by value. After all, if you give someone a crate with a couple of boxes
inside, you are also giving him the boxes. You're not just giving him a
picture of the boxes or a key to a locker where the boxes are to be
found (a.k.a., a pointer); you are giving him the actual boxes, a.k.a.,
the actual value of the array.

Richard
James Kuyper
2017-04-01 02:47:02 UTC
Permalink
Post by s***@gmail.com
So lately I've came up to this again - and to be quite honest
recently I've kinda accepted the form f(int a[]) as a shorthand for
f(int *a), as part of what the C language is (the same way it was
natural for me, before, to know that a file scope declaration using
the static storage specifier indicates internal linkage but a block
scope one, using the same identifier - storage duration).
However I still don't think this property, this shortcut for writing
complex otherwise declarations (imagine if you want to pass a pointer
to array) should be something more than this - a shortcut.
What I feel like is that we must banish all other forms except the way
an "incomplete array of ..." can be declared as a function parameter
to actually mean "pointer to ...". ...
Note: this adjustment is not restricted to incomplete arrays, it applies
just as much to "int ptr[3]" as it does to "int ptr[]".
Post by s***@gmail.com
... I also feel like we should allow
this only in a direct declarator. ...
So you don't want it to be allowed in direct-abstract-declarators, such as

void myfunc(int[5]);

?
Post by s***@gmail.com
... So for example f(int (a)[]) would
be a constraint violation (maybe we could make use of that form in
the future?).
Normally, a parenthesized declarator is considered the same as the
unparenthesized declarator (6.7.6p6). You want to make an exception to
this rule for the special case of function parameters declared with
array type?
Post by s***@gmail.com
The constraint violation should also apply for parameter declarations
using a typedef name to specify the array type.
I see the benefits of using [] as an alternative syntax for * when
writing pointer to arrays (useful when passing multidimensional
arrays) - consider writing f(int (*p)[2]) vs f(int p[][2]) - (I feel
like the second variant is easier to edit.) ...
... However I feel like it
should be implemented rather as a simple text replacement than whole
type system change. Now I know that the preprocessor is another
monster but I doubt that implementing a check for direct array
declarator of incomplete type will be that expensive to the parser.
A key problem is that the rule as currently written refers to
"declarations", "parameters", and "arrays", none of which exist until
translation phase 7. Preprocessing takes place during translation phase
4, when all you have is an unanalyzed sequence of pre-processing tokens.
If something similar to the existing rule were re-written to be a simple
text replacement process similar to macro replacements, the rule would
be incapable of distinguishing based upon any of those concepts. It
would have to say something like "identifier [ ]" gets automatically
replaced by "(*identifier)". It couldn't do a context-sensitive
replacement, because the relevant context won't be parsed until
translation phase 7.

This leads to changing the meaning of the following code:

1. This is a file scope declaration of array with external linkage of
unspecified length. The actual length is determined by definition of the
array, which is likely to be in a different translation unit:

extern int array[];

By changing this rule to be a simple text-replacement rule, that
declaration would become equivalent to:

extern int (*array);

2. This is a flexible array member:

struct counted_string{
size_t size;
char string[];
}

your text replacement rule would make it become equivalent to:

struct counted_string{
size_t size;
char *string;
}

Defining it as a text replacement would also allow it to be used in
contexts that have nothing to do with declarations, and would in fact be
syntax errors or constraint violations under the current rules, because
the text replace would take place before the syntax rule or constraint
can be applied. The

double *d = &pi;
d[] = 3.14;

would, by text replacement, become

double *d = &pi;
(*d) = 3.14;

which is perfectly legal.

I suspect, given the four statements that I responded to above, that you
don't have a firm grasp on all of the relevant jargon, and that you
therefore mean something different from what you've actually said. It
would help to clarify exactly what you mean, if you could provide a
wider range of examples which are permitted under the current rules,
which would be prohibited under your proposed change to the rules.
Francis Glassborow
2017-04-01 11:59:54 UTC
Permalink
On 01/04/2017 03:47, James Kuyper wrote:
< I seem to have snipped rather too much but I think my point actually
is close to free standing/>
Post by James Kuyper
I suspect, given the four statements that I responded to above, that you
don't have a firm grasp on all of the relevant jargon, and that you
therefore mean something different from what you've actually said. It
would help to clarify exactly what you mean, if you could provide a
wider range of examples which are permitted under the current rules,
which would be prohibited under your proposed change to the rules.
And when doing that note that there is exactly zero chance of a change
to C that breaks large quantities of existing code. If the OP cannot
live with the status quo (s)he needs to find another language.
There are many theoretical good ideas that fall foul of the need to
support legacy code. Some other languages have little respect for the
past but that also creates problems. If you want a language that will
compile your code both now and in 10 years time you need this respect
for the past.

Francis
s***@gmail.com
2017-04-01 14:52:26 UTC
Permalink
Post by James Kuyper
Post by s***@gmail.com
So lately I've came up to this again - and to be quite honest
recently I've kinda accepted the form f(int a[]) as a shorthand for
f(int *a), as part of what the C language is (the same way it was
natural for me, before, to know that a file scope declaration using
the static storage specifier indicates internal linkage but a block
scope one, using the same identifier - storage duration).
However I still don't think this property, this shortcut for writing
complex otherwise declarations (imagine if you want to pass a pointer
to array) should be something more than this - a shortcut.
What I feel like is that we must banish all other forms except the way
an "incomplete array of ..." can be declared as a function parameter
to actually mean "pointer to ...". ...
Note: this adjustment is not restricted to incomplete arrays, it applies
just as much to "int ptr[3]" as it does to "int ptr[]".
Post by s***@gmail.com
... I also feel like we should allow
this only in a direct declarator. ...
So you don't want it to be allowed in direct-abstract-declarators, such as
void myfunc(int[5]);
?
Post by s***@gmail.com
... So for example f(int (a)[]) would
be a constraint violation (maybe we could make use of that form in
the future?).
Normally, a parenthesized declarator is considered the same as the
unparenthesized declarator (6.7.6p6). You want to make an exception to
this rule for the special case of function parameters declared with
array type?
Post by s***@gmail.com
The constraint violation should also apply for parameter declarations
using a typedef name to specify the array type.
I see the benefits of using [] as an alternative syntax for * when
writing pointer to arrays (useful when passing multidimensional
arrays) - consider writing f(int (*p)[2]) vs f(int p[][2]) - (I feel
like the second variant is easier to edit.) ...
... However I feel like it
should be implemented rather as a simple text replacement than whole
type system change. Now I know that the preprocessor is another
monster but I doubt that implementing a check for direct array
declarator of incomplete type will be that expensive to the parser.
A key problem is that the rule as currently written refers to
"declarations", "parameters", and "arrays", none of which exist until
translation phase 7. Preprocessing takes place during translation phase
4, when all you have is an unanalyzed sequence of pre-processing tokens.
If something similar to the existing rule were re-written to be a simple
text replacement process similar to macro replacements, the rule would
be incapable of distinguishing based upon any of those concepts. It
would have to say something like "identifier [ ]" gets automatically
replaced by "(*identifier)". It couldn't do a context-sensitive
replacement, because the relevant context won't be parsed until
translation phase 7.
1. This is a file scope declaration of array with external linkage of
unspecified length. The actual length is determined by definition of the
extern int array[];
By changing this rule to be a simple text-replacement rule, that
extern int (*array);
struct counted_string{
size_t size;
char string[];
}
struct counted_string{
size_t size;
char *string;
}
Defining it as a text replacement would also allow it to be used in
contexts that have nothing to do with declarations, and would in fact be
syntax errors or constraint violations under the current rules, because
the text replace would take place before the syntax rule or constraint
can be applied. The
double *d = &pi;
d[] = 3.14;
would, by text replacement, become
double *d = &pi;
(*d) = 3.14;
which is perfectly legal.
I suspect, given the four statements that I responded to above, that you
don't have a firm grasp on all of the relevant jargon, and that you
therefore mean something different from what you've actually said. It
would help to clarify exactly what you mean, if you could provide a
wider range of examples which are permitted under the current rules,
which would be prohibited under your proposed change to the rules.
Have you read my last sentence: "Now I know that the preprocessor is another monster but I doubt that implementing a check for direct array declarator of incomplete type will be that expensive to the **parser**. " - I give you one last chance.
James R. Kuyper
2017-04-01 15:07:16 UTC
Permalink
...
Post by s***@gmail.com
Post by James Kuyper
Post by s***@gmail.com
... However I feel like it
should be implemented rather as a simple text replacement than whole
type system change. Now I know that the preprocessor is another
monster but I doubt that implementing a check for direct array
declarator of incomplete type will be that expensive to the parser.
A key problem is that the rule as currently written refers to
"declarations", "parameters", and "arrays", none of which exist until
translation phase 7. Preprocessing takes place during translation phase
4, when all you have is an unanalyzed sequence of pre-processing tokens.
If something similar to the existing rule were re-written to be a simple
text replacement process similar to macro replacements, the rule would
be incapable of distinguishing based upon any of those concepts. It
would have to say something like "identifier [ ]" gets automatically
replaced by "(*identifier)". It couldn't do a context-sensitive
replacement, because the relevant context won't be parsed until
translation phase 7.
1. This is a file scope declaration of array with external linkage of
unspecified length. The actual length is determined by definition of the
extern int array[];
By changing this rule to be a simple text-replacement rule, that
extern int (*array);
struct counted_string{
size_t size;
char string[];
}
struct counted_string{
size_t size;
char *string;
}
Defining it as a text replacement would also allow it to be used in
contexts that have nothing to do with declarations, and would in fact be
syntax errors or constraint violations under the current rules, because
the text replace would take place before the syntax rule or constraint
can be applied. The
double *d = &pi;
d[] = 3.14;
would, by text replacement, become
double *d = &pi;
(*d) = 3.14;
which is perfectly legal.
I suspect, given the four statements that I responded to above, that you
don't have a firm grasp on all of the relevant jargon, and that you
therefore mean something different from what you've actually said. It
would help to clarify exactly what you mean, if you could provide a
wider range of examples which are permitted under the current rules,
which would be prohibited under your proposed change to the rules.
Have you read my last sentence: "Now I know that the preprocessor is another monster but I doubt that implementing a check for direct array declarator of incomplete type will be that expensive to the **parser**. "
Yes. The preceding discussion was in fact prompted by my reading of that
sentence, as well as by my failure to figure out what you meant by that
sentence. That's why I posted that discussion immediately after quoting
that sentence. That discussion was intended to provide a framework that
you could respond to by explaining in more detail what you actually
meant. Is there any particular reason why you failed to do so?
Post by s***@gmail.com
- I give you one last chance.
One last chance to do what?
s***@gmail.com
2017-04-01 15:49:21 UTC
Permalink
Post by James R. Kuyper
...
Post by s***@gmail.com
Post by James Kuyper
Post by s***@gmail.com
... However I feel like it
should be implemented rather as a simple text replacement than whole
type system change. Now I know that the preprocessor is another
monster but I doubt that implementing a check for direct array
declarator of incomplete type will be that expensive to the parser.
A key problem is that the rule as currently written refers to
"declarations", "parameters", and "arrays", none of which exist until
translation phase 7. Preprocessing takes place during translation phase
4, when all you have is an unanalyzed sequence of pre-processing tokens.
If something similar to the existing rule were re-written to be a simple
text replacement process similar to macro replacements, the rule would
be incapable of distinguishing based upon any of those concepts. It
would have to say something like "identifier [ ]" gets automatically
replaced by "(*identifier)". It couldn't do a context-sensitive
replacement, because the relevant context won't be parsed until
translation phase 7.
1. This is a file scope declaration of array with external linkage of
unspecified length. The actual length is determined by definition of the
extern int array[];
By changing this rule to be a simple text-replacement rule, that
extern int (*array);
struct counted_string{
size_t size;
char string[];
}
struct counted_string{
size_t size;
char *string;
}
Defining it as a text replacement would also allow it to be used in
contexts that have nothing to do with declarations, and would in fact be
syntax errors or constraint violations under the current rules, because
the text replace would take place before the syntax rule or constraint
can be applied. The
double *d = &pi;
d[] = 3.14;
would, by text replacement, become
double *d = &pi;
(*d) = 3.14;
which is perfectly legal.
I suspect, given the four statements that I responded to above, that you
don't have a firm grasp on all of the relevant jargon, and that you
therefore mean something different from what you've actually said. It
would help to clarify exactly what you mean, if you could provide a
wider range of examples which are permitted under the current rules,
which would be prohibited under your proposed change to the rules.
Have you read my last sentence: "Now I know that the preprocessor is another monster but I doubt that implementing a check for direct array declarator of incomplete type will be that expensive to the **parser**. "
Yes. The preceding discussion was in fact prompted by my reading of that
sentence, as well as by my failure to figure out what you meant by that
sentence. That's why I posted that discussion immediately after quoting
that sentence. That discussion was intended to provide a framework that
you could respond to by explaining in more detail what you actually
meant. Is there any particular reason why you failed to do so?
Post by s***@gmail.com
- I give you one last chance.
One last chance to do what?
To read my sentence :). Anyway. Well I meant we should allow only "sensible" declarations like:


int f(int [][2]), f(int [][*][*]), f(int [static 6]),

f(int a[][2]), f(int b[][*][*]), f(int c[static 6]);

Which basically are declaring any function parameter as "incomplete array of" or any other array type if there is a `static` keyword inside the `[` and `]` of the array type derivation.

But not confusing and unnecessary ones like:

int f1(int [2]), f1(int [*]),

f1(int a[2]), f1(int b[*]);

typedef int tar[6];

int f2(tar), f2(tar a), f2(int [2]), f2(int b[2]);

Note that I've used both declarators and abstract declarators in the above examples.
James R. Kuyper
2017-04-01 16:29:14 UTC
Permalink
...
Post by s***@gmail.com
Post by James R. Kuyper
Post by s***@gmail.com
Have you read my last sentence: "Now I know that the preprocessor is another monster but I doubt that implementing a check for direct array declarator of incomplete type will be that expensive to the **parser**. "
Yes. The preceding discussion was in fact prompted by my reading of that
sentence, as well as by my failure to figure out what you meant by that
sentence. That's why I posted that discussion immediately after quoting
that sentence. That discussion was intended to provide a framework that
you could respond to by explaining in more detail what you actually
meant. Is there any particular reason why you failed to do so?
Post by s***@gmail.com
- I give you one last chance.
One last chance to do what?
int f(int [][2]), f(int [][*][*]), f(int [static 6]),
f(int a[][2]), f(int b[][*][*]), f(int c[static 6]);
Which basically are declaring any function parameter as "incomplete array of" or any other array type if there is a `static` keyword inside the `[` and `]` of the array type derivation.
int f1(int [2]), f1(int [*]),
f1(int a[2]), f1(int b[*]);
typedef int tar[6];
int f2(tar), f2(tar a), f2(int [2]), f2(int b[2]);
Note that I've used both declarators and abstract declarators in the above examples.
Keep in mind that, because such declarations are currently allowed, a
lot of code has already been written to deliberately use such
declarations. All of that code would break if such a change were made.
The committee has strong policies in favor of maintaining backwards
compatibility. Changes that convert existing code with well-defined
behavior into code which has undefined behavior or different defined
behavior are approved only if the committee believes that they are very
well motivated (the committee's judgement on such matters is, of course,
often hotly contested). I doubt that your motivation for this change is
good enough for the committee to ever approve it.

I personally find the ability to declare

int calculate_azimuth(double solar_azimuth[FRAMES][DETECTORS])

to be convenient, if only as documentation that the pointer passed to
this routine should point at an array containing at least FRAMES
elements (of course,the real routine I'm thinking of has a much more
complicated interface). Under the current rules, that documentation is
ignored by the compiler. If anything, I'd prefer a change that required
the compiler to issue a diagnostic if calculate_azimuth() were passed a
pointer to an array with fewer than FRAMES elements. "static" doesn't
quite do that, though I expect the better compilers to use "static" as
an excuse for issuing such diagnostics.
s***@casperkitty.com
2017-04-02 15:52:10 UTC
Permalink
Post by James R. Kuyper
Keep in mind that, because such declarations are currently allowed, a
lot of code has already been written to deliberately use such
declarations. All of that code would break if such a change were made.
If the Standard were to say that something is no longer required to be
treated as part of the language, but implementations may support it as an
optional extension, that would maintain compatibility with existing code.
Further, in cases where there would be an obvious way to adjust the code
to would work under *both* the old and new rules, and if the adjusted code
would by most measures be considered at least as good as the original,
that would be just about the least harmful form of incompatibility.

Further, in cases where a construct would be treated differently by different
implementations, having an alternative form for each treatment would make it
easier to port code among different implementations. For example, given:

uint32_t x,y,z; // Assume values assigned somehow
int result = (x-y) > z;

the code would be legal on systems where "int" is 32 bits, and on those where
it is 64 bits, but it would have different defined meanings on the different
systems. If such code were in the "optionally supported" category, porting
to systems with different sizes of "int" could be facilitated by having a
compiler reject expressions that would have different defined meanings on
systems with different integer sizes.
Post by James R. Kuyper
The committee has strong policies in favor of maintaining backwards
compatibility. Changes that convert existing code with well-defined
behavior into code which has undefined behavior or different defined
behavior are approved only if the committee believes that they are very
well motivated (the committee's judgement on such matters is, of course,
often hotly contested). I doubt that your motivation for this change is
good enough for the committee to ever approve it.
Changes that would cause code to go from having defined behavior to having
Undefined Behavior were not regarded as breaking changes, since compiler
writers are presumed to have the judgment necessary to support the kinds
of code people will want to run on them.

Under C89, for example, the Common Initial Sequence guarantee defined
behaviors in some cases where it would not be defined under C99. Certain
kinds of vector-processing code benefit from being able to have distinct
"position" and "velocity" types which have the same members but are not
presumed to be alias-compatible. Compilers which are developed to exploit
that presumption may not be able to run code which relies upon the CIS
guarantees, but such code should continue to work just fine on any existing
compilers that aren't modified to break it.
Keith Thompson
2017-04-02 19:10:56 UTC
Permalink
Post by s***@casperkitty.com
Post by James R. Kuyper
Keep in mind that, because such declarations are currently allowed, a
lot of code has already been written to deliberately use such
declarations. All of that code would break if such a change were made.
If the Standard were to say that something is no longer required to be
treated as part of the language, but implementations may support it as an
optional extension, that would maintain compatibility with existing code.
A statement that "implementations may support it as an optional
extension" would be meaningless. Implementations are already permitted
to support *anything* as an optional extension.

C99 removed implicit int. That change broke compatibility for programs
that depended on it.
Post by s***@casperkitty.com
Further, in cases where there would be an obvious way to adjust the code
to would work under *both* the old and new rules, and if the adjusted code
would by most measures be considered at least as good as the original,
that would be just about the least harmful form of incompatibility.
Yes, removing implicit int is an example of that.

[...]
Post by s***@casperkitty.com
Changes that would cause code to go from having defined behavior to having
Undefined Behavior were not regarded as breaking changes, since compiler
writers are presumed to have the judgment necessary to support the kinds
of code people will want to run on them.
Were not regarded by whom?

[...]
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
s***@casperkitty.com
2017-04-02 21:45:03 UTC
Permalink
Post by Keith Thompson
Post by s***@casperkitty.com
Post by James R. Kuyper
Keep in mind that, because such declarations are currently allowed, a
lot of code has already been written to deliberately use such
declarations. All of that code would break if such a change were made.
If the Standard were to say that something is no longer required to be
treated as part of the language, but implementations may support it as an
optional extension, that would maintain compatibility with existing code.
A statement that "implementations may support it as an optional
extension" would be meaningless. Implementations are already permitted
to support *anything* as an optional extension.
The intention of the statement, though I didn't write it in standardese,
would be to require that implementations do one of two things when given
a program that uses the feature:

1. Implement the feature with the Standard-defined syntax and semantics.

2. Regard the attempted use of the unsupported feature as a constraint
violation.

In particular, it would forbid an implementation from silently accepting code
which uses the feature, but then failing to provide the semantics indicated
thereby.

There are a number of features which are useful in many programs, but would
increase the difficulty of implementing C on some smaller platforms where it
would otherwise be a useful language for writing programs that don't need
those features.
Post by Keith Thompson
C99 removed implicit int. That change broke compatibility for programs
that depended on it.
Post by s***@casperkitty.com
Further, in cases where there would be an obvious way to adjust the code
to would work under *both* the old and new rules, and if the adjusted code
would by most measures be considered at least as good as the original,
that would be just about the least harmful form of incompatibility.
Yes, removing implicit int is an example of that.
[...]
Post by s***@casperkitty.com
Changes that would cause code to go from having defined behavior to having
Undefined Behavior were not regarded as breaking changes, since compiler
writers are presumed to have the judgment necessary to support the kinds
of code people will want to run on them.
Were not regarded by whom?
By the authors of the Standard. Read the rationale for promoting short
unsigned types to signed int, and I think it's pretty clear that they
expected that if code had been written to expect unsigned promotion, they
would have regarded the altered semantics of:

if (byte1 - byte2 > maxdelta) ...

as a breaking change, but would not have regarded the semantics of

word1 = byte1*byte2;

as a breaking change, at least not on systems that use silent-wraparound
two's-complement arithmetic, since the authors of the Standard note that on
such systems the behavior of the latter code would be unaffected by the
change to signed int *even in cases involving integer overflow*.
Keith Thompson
2017-04-02 22:21:16 UTC
Permalink
Post by s***@casperkitty.com
Post by Keith Thompson
Post by s***@casperkitty.com
Post by James R. Kuyper
Keep in mind that, because such declarations are currently allowed, a
lot of code has already been written to deliberately use such
declarations. All of that code would break if such a change were made.
If the Standard were to say that something is no longer required to be
treated as part of the language, but implementations may support it as an
optional extension, that would maintain compatibility with existing code.
A statement that "implementations may support it as an optional
extension" would be meaningless. Implementations are already permitted
to support *anything* as an optional extension.
The intention of the statement, though I didn't write it in standardese,
would be to require that implementations do one of two things when given
1. Implement the feature with the Standard-defined syntax and semantics.
2. Regard the attempted use of the unsupported feature as a constraint
violation.
Then that's not an extension, it's an optional feature.

So you propose taking an existing standard mandatory feature (certain
kinds of array parameter declaration) and turning it into an optional
feature, supported or not at the whim of each compiler provider.

Ick.
Post by s***@casperkitty.com
In particular, it would forbid an implementation from silently accepting code
which uses the feature, but then failing to provide the semantics indicated
thereby.
There are a number of features which are useful in many programs, but would
increase the difficulty of implementing C on some smaller platforms where it
would otherwise be a useful language for writing programs that don't need
those features.
I don't believe that array parameters (more precisely, parameters using
array syntax) are such a feature.

[...]
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
j***@verizon.net
2017-04-02 19:40:39 UTC
Permalink
Post by s***@casperkitty.com
Post by James R. Kuyper
Keep in mind that, because such declarations are currently allowed, a
lot of code has already been written to deliberately use such
declarations. All of that code would break if such a change were made.
If the Standard were to say that something is no longer required to be
treated as part of the language, but implementations may support it as an
optional extension, that would maintain compatibility with existing code.
Nonsense. There's always some existing code which relies upon the standard's
guarantee that the feature is supported. Remove that guarantee, making support
optional, allows such code to break when ported to implementations which choose
the option of not supporting it, implementations that it would otherwise have
been guaranteed to be portable to. That is not consistent with any definition
of backwards compatibility that I'm familiar with.
Post by s***@casperkitty.com
Further, in cases where there would be an obvious way to adjust the code
to would work under *both* the old and new rules, and if the adjusted code
would by most measures be considered at least as good as the original,
that would be just about the least harmful form of incompatibility.
I suppose, but it's still substantially more harmful than is considered
acceptable. People should not have to re-write code to work with later versions
of the standard - that's what backwards compatibility is all about. The
committee has occasionally violated backwards compatibility, but it quite
rightly insists that there be a very good reason for doing so. It would be
pretty odd if I happened to agree perfectly with the committee on every one of
those decisions, and I don't, but I do agree with the principle that those
decisions should be decided according to those criteria.
Post by s***@casperkitty.com
Post by James R. Kuyper
The committee has strong policies in favor of maintaining backwards
compatibility. Changes that convert existing code with well-defined
behavior into code which has undefined behavior or different defined
behavior are approved only if the committee believes that they are very
well motivated (the committee's judgement on such matters is, of course,
often hotly contested). I doubt that your motivation for this change is
good enough for the committee to ever approve it.
Changes that would cause code to go from having defined behavior to having
Undefined Behavior were not regarded as breaking changes, ...
Nonsense. Nothing could possibly be more broken than conversion of defined
behavior to undefined behavior. Undefined behavior means a fully conforming
implementation can do anything, including giving the code behavior that bears
no meaningful similarity to the behavior it used to be defined as having.
Counting on implementations to continue supporting behavior they're no longer
required to support misses the whole point of imposing a requirement in the
first place. If they're supposed to do that, there's no point in removing the
requirement. If they're not supposed to do that, then removing the requirement
is exactly the worst possible way of communicating that fact. It's sort of like
saying "Kill me!" when what you really mean is "Don't kill me!".
James R. Kuyper
2017-04-02 19:52:03 UTC
Permalink
On 04/02/2017 03:40 PM, ***@verizon.net wrote:
...
Post by j***@verizon.net
Counting on implementations to continue supporting behavior they're no longer
required to support misses the whole point of imposing a requirement in the
first place. If they're supposed to do that, there's no point in removing the
requirement. If they're not supposed to do that, then removing the requirement
is exactly the worst possible way of communicating that fact. It's sort of like
saying "Kill me!" when what you really mean is "Don't kill me!".
I got a little confused while writing that. Here's a more coherent re-write:

"... If they're supposed to continue supporting that feature, there's no
point in removing the requirement. Removing it is exactly the worst
possible way of communicating that fact. It's sort of like saying "Kill
me!" when what you mean really means is "Don't kill me!".
Removing the requirement is reasonable only if implementations are
supposed to feel free to remove support for the feature - despite the
fact that doing so will break code that relied upon such support. You
should admit that such a change would cause backwards incompatibility,
rather than hiding behind a claim that implementations will voluntarily
choose to continue supporting features they're no longer required to
support.
s***@casperkitty.com
2017-04-02 22:13:45 UTC
Permalink
Post by James R. Kuyper
"... If they're supposed to continue supporting that feature, there's no
point in removing the requirement. Removing it is exactly the worst
possible way of communicating that fact. It's sort of like saying "Kill
me!" when what you mean really means is "Don't kill me!".
Removing the requirement is reasonable only if implementations are
supposed to feel free to remove support for the feature - despite the
fact that doing so will break code that relied upon such support. You
should admit that such a change would cause backwards incompatibility,
rather than hiding behind a claim that implementations will voluntarily
choose to continue supporting features they're no longer required to
support.
Some features and behavioral guarantees can be supported at almost no cost
on some implementations, but would be extremely expensive or problematic
to support on others.

Further, some such features are extremely useful, if not essential, in some
programming fields, but offer no value in others.

I would suggest that it does not make sense to require that implementations
specialized for application fields where a feature would be worthless and
targets where they would be expensive, should be required to endure the cost
of supporting the useless features.

On the other hand, it also does not make sense to require that programmers
who are exclusively interested in supporting platforms where features would
be cheap to support, should be required to refrain from using those features
because they would be unsupportable on platforms to which the code would
never be ported anyway.

I views the meaning of "Undefined Behavior" as "Do whatever would best
serve the implementation's intended purpose". It certainly would allow an
implementation to do that, and I see reason to expect that an implementer
would make an implementation behave contrary to its intended purpose. The
only tricky part is figuring out what the "intended purpose" of some
implementations *is*.

The reason a C Standard was necessary wasn't so much to describe the ways
in which all implementations for commonplace platforms behaved identically,
but rather to either unify the few places where they needlessly differed
(most notably variations in the standard headers), and to identify how the
language should be adapted to unusual platforms or application fields. If
90% of implementations handle something a certain way, and that's useful
for most kinds of applications on most platforms, but mandating the
behavior would make the language less suitable for writing some kinds of
applications on some platforms, the logical thing is to have implementations
support the feature when practical given their platform and application
field.
Keith Thompson
2017-04-02 22:22:44 UTC
Permalink
***@casperkitty.com writes:
[...]
Post by s***@casperkitty.com
I views the meaning of "Undefined Behavior" as "Do whatever would best
serve the implementation's intended purpose".
Then your view of that phrase's meaning has very little similarity to
its actual definition.
Post by s***@casperkitty.com
It certainly would allow an
implementation to do that, and I see reason to expect that an implementer
would make an implementation behave contrary to its intended purpose. The
only tricky part is figuring out what the "intended purpose" of some
implementations *is*.
That "tricky part" goes away if you acknowledge what "undefined
behavior" really means.

[...]
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
s***@casperkitty.com
2017-04-02 23:14:26 UTC
Permalink
Post by Keith Thompson
[...]
Post by s***@casperkitty.com
I views the meaning of "Undefined Behavior" as "Do whatever would best
serve the implementation's intended purpose".
Then your view of that phrase's meaning has very little similarity to
its actual definition.
Anything an implementation might do would either best serve its intended
purpose (bearing in mind that "purpose" may sometimes include "get the
thing shipped") or be sub-optimal for that purpose.

The authors of the Standard gave some examples of things implementations
might do, including "behave in a documented fashion characteristic of the
environment". Why would they have listed such a thing if they didn't
expect that implementations would do so when it would suit their intended
purpose?
Post by Keith Thompson
Post by s***@casperkitty.com
It certainly would allow an
implementation to do that, and I see reason to expect that an implementer
would make an implementation behave contrary to its intended purpose. The
only tricky part is figuring out what the "intended purpose" of some
implementations *is*.
That "tricky part" goes away if you acknowledge what "undefined
behavior" really means.
It's certainly possible that people might sometimes accidentally write
implementations which behave in ways that do not best serve their intended
purpose, but if an implementer deliberately writes an implementation to
behave a certain way, such action would suggest that the intended purpose
of the implementation was something that would be served by such action.
Francis Glassborow
2017-04-03 13:31:28 UTC
Permalink
Post by s***@casperkitty.com
Post by Keith Thompson
[...]
Post by s***@casperkitty.com
I views the meaning of "Undefined Behavior" as "Do whatever would best
serve the implementation's intended purpose".
Then your view of that phrase's meaning has very little similarity to
its actual definition.
Anything an implementation might do would either best serve its intended
purpose (bearing in mind that "purpose" may sometimes include "get the
thing shipped") or be sub-optimal for that purpose.
The authors of the Standard gave some examples of things implementations
might do, including "behave in a documented fashion characteristic of the
environment". Why would they have listed such a thing if they didn't
expect that implementations would do so when it would suit their intended
purpose?
Post by Keith Thompson
Post by s***@casperkitty.com
It certainly would allow an
implementation to do that, and I see reason to expect that an implementer
would make an implementation behave contrary to its intended purpose. The
only tricky part is figuring out what the "intended purpose" of some
implementations *is*.
That "tricky part" goes away if you acknowledge what "undefined
behavior" really means.
It's certainly possible that people might sometimes accidentally write
implementations which behave in ways that do not best serve their intended
purpose, but if an implementer deliberately writes an implementation to
behave a certain way, such action would suggest that the intended purpose
of the implementation was something that would be served by such action.
Undefined behaviour deliberately places no requirements on an
implementer. So an implementer might just choose to do nothing. The
consequence of doing nothing can be disastrous.
There is nothing that requires an implementer to ship a Standard
conforming compiler. Indeed there are many useful compilers for C like
languages that explicitly do not conform to the Standard but their
implementers have the courtesy to document that they are not Standard C
compilers and that they have replaced certain Standard behaviours with
those that are desired by their customers (think of embedded code and
micro-controllers)
Many (most?) compilers are only conforming with some combination of
switches (I believe that technically every selection of switches results
in a different compiler).
Actually it is the job of tools such as Lint to identify dangerous
constructs so instead of campaigning for changes to the Standard,
campaign for better tools.

Francis
s***@casperkitty.com
2017-04-03 15:45:14 UTC
Permalink
Post by Francis Glassborow
Post by s***@casperkitty.com
It's certainly possible that people might sometimes accidentally write
implementations which behave in ways that do not best serve their intended
purpose, but if an implementer deliberately writes an implementation to
behave a certain way, such action would suggest that the intended purpose
of the implementation was something that would be served by such action.
Undefined behaviour deliberately places no requirements on an
implementer. So an implementer might just choose to do nothing. The
consequence of doing nothing can be disastrous.
If "doing nothing" would best help an implementation serve its intended
purpose, then the implementation should do nothing. "Ignoring" Undefined
Behavior is one of the listed consequences in the Standard, though the
meaning of "ignore" seems to have shifted.

On two's-complement silent-wraparound hardware, what does it mean for a
compiler to "ignore" the possible overflow in:

unsigned mul(unsigned short x, unsigned short y)
{
return (x*y) & 65535;
}

Does it mean that the compiler will perform the multiplication in whatever
fashion the system does integer multiplies, without regard for whether there
might be a signed-integer overflow, and then mask of and return the bottom
16 bits of the result? That's what the authors of the Standard would have
expected silent-wraparound implementations to do (they explicitly say as much
in their rationale for making short unsigned types promote to signed). Or
does it mean that the system will identify inputs where the product would
exceed 2147483647 and decide that they cannot occur?
Post by Francis Glassborow
There is nothing that requires an implementer to ship a Standard
conforming compiler. Indeed there are many useful compilers for C like
languages that explicitly do not conform to the Standard but their
implementers have the courtesy to document that they are not Standard C
compilers and that they have replaced certain Standard behaviours with
those that are desired by their customers (think of embedded code and
micro-controllers)
It is also possible to have conforming implementaitons which are suitable
for low-level programming because they guarantee behaviors beyond those
required by the Standard, and to have conforming compilers which are not
suitable for that purpose.
Post by Francis Glassborow
Many (most?) compilers are only conforming with some combination of
switches (I believe that technically every selection of switches results
in a different compiler).
Indeed. GCC is non-conforming except in -fno-strict-aliasing mode, for
example, because early stages of code generation eliminate as redundant
assignments which can never directly lead to code generation, but should
block optimization-related assumptions. For example, on a system where
both "long" and "long long" are 64 bits, given the sequence:

#include <string.h>

long test(long *p1, long long *p2, long long *p3)
{
long temp;
*p1 = 1;
*p2 = 2;
memcpy(&temp,p3,sizeof *p3);
memcpy(&p3,&temp,sizeof *p3);
return *p1;
}

the version of gcc7-x64 on godbolt generates code equivalent to:

long test(long *p1, long long *p2, long long *p3)
{
*p1 = 1;
*p2 = 2;
return 1;
}

even though if p1==p2 and p1==p3 the Standard would define the behavior as
returning 2 on systems where "long" and "long long" have identical
representations. The effective type of "temp" is long, and the first
"memcpy" should set all of its bytes to match those of whatever p3 points
to. The second memcpy should then write those back, but in such a way
that they can be read back as "long". There's no need for the memcpy to
actually read and write the bytes, since the values will be the same after
the second memcpy as they were before the first, *but* that does not mean
that a compiler would be entitled to ignore the memcpy operations
altogether.
Post by Francis Glassborow
Actually it is the job of tools such as Lint to identify dangerous
constructs so instead of campaigning for changes to the Standard,
campaign for better tools.
The problem is that effective low-level programming on some platforms requires
features and guarantees which would be expensive or impractical on other
platforms and might offer little benefit in other application fields. There
is thus no way that a single language dialect would be able to accommodate
all needs of some implementations' users without imposing needless burdens
on other implementations.

If one adapts the principle that implementations which are intended to be
suitable for low-level programming should expose documented behaviors of
the underlying environment in cases where such behaviors may be useful and
there would be no compelling reason to do otherwise in the general case,
such a principle would for many platforms effectively define a C dialect
which would be much more powerful than that mandated by the Standard. It
would be difficult for a Standard to officially express such a concept,
however, since (1) it would rely upon an implementer's judgment as to what
would constitute a "compelling reason", and (2) an implementer who would
exercise good judgment in such matters could probably produce such an
implementation without being instructed to by a Standard, especially
since *that's what implementers had been doing for over a decade before
the Standard was written*.

What is unfortunate is that the Standard didn't define more directives which
would not have required most existing systems to do anything that they
weren't already doing, but would allow programmers to demand such behavior
when needed, or would allow compilers to make optimizations to which they
would not otherwise be entitled. If, for example, C89 had said that the
behavior of code which writes storage as one type and reads it as another
in ways other than those explicitly accommodated by the Standard will only
be defined if an __aliasing_barrier directive is used between the write and
the read, and that any code which performs cross-type operations *including
those allowed by the Standard* but lacks the appropriate directives should
be considered deprecated, then aliasing shouldn't need to pose a problem.
Code which relies upon aliasing without the directives could fairly be
called "broken", but compilers could make many optimizations which are not
presently possible.
j***@verizon.net
2017-04-02 22:33:15 UTC
Permalink
Post by s***@casperkitty.com
Post by James R. Kuyper
"... If they're supposed to continue supporting that feature, there's no
point in removing the requirement. Removing it is exactly the worst
possible way of communicating that fact. It's sort of like saying "Kill
me!" when what you mean really means is "Don't kill me!".
Removing the requirement is reasonable only if implementations are
supposed to feel free to remove support for the feature - despite the
fact that doing so will break code that relied upon such support. You
should admit that such a change would cause backwards incompatibility,
rather than hiding behind a claim that implementations will voluntarily
choose to continue supporting features they're no longer required to
support.
Some features and behavioral guarantees can be supported at almost no cost
on some implementations, but would be extremely expensive or problematic
to support on others.
That should not apply to any feature which has ever actually been mandatory. If
it did, that feature should not have been made mandatory in the first place. Of
course, technology changes, and if it has changed in such a way that what used
to be trivial to implement on all platforms has become difficult to implement
on some platforms, your first question should be - "What idiot decided to do
that?". However, there might be a good reason for doing it, and if so that
could be a legitimate reason for breaking backwards compatibility - but you
should admit that backwards compatibility is being broken: code that used to be
portable to any conforming implementation of C can no longer be ported to some
of them.
In any event, there is not even the remotest possibility of this applying to
feature currently under discussion.
s***@casperkitty.com
2017-04-03 00:22:57 UTC
Permalink
Post by j***@verizon.net
That should not apply to any feature which has ever actually been mandatory. If
it did, that feature should not have been made mandatory in the first place. Of
course, technology changes, and if it has changed in such a way that what used
to be trivial to implement on all platforms has become difficult to implement
on some platforms, your first question should be - "What idiot decided to do
that?". However, there might be a good reason for doing it, and if so that
could be a legitimate reason for breaking backwards compatibility - but you
should admit that backwards compatibility is being broken: code that used to be
portable to any conforming implementation of C can no longer be ported to some
of them.
For various, likely political, reasons, I think the authors of the Standard
are loath to address issues of what makes an implementation be of high or
low quality. On the other hand, I would expect that makers of high-quality
implementations would seek to support the kinds of code their customers would
want to run, whether or not the Standard mandated it.

As far as I'm concerned, if an implementation would be useful for many
purposes even though it can't uphold a guarantee mandated by the Standard,
and if it would be useful to have the Standard describe other aspects of
the implementation, then the Standard should be written in such a way that
it can usefully describe those other aspects. That would be especially
true if the Standard were focused on defining classes of things about which
it can make concrete statements.

For example, suppose the Standard were to establish a concept of Selectively-
Conforming programs and Minimally Safely Conforming implementations such that
any implementation which is at least Minimally Safely Conforming, given any
program that is Selectively Conforming, must either reject the program (in
Implementation-Defined fashion) or else process it in a fashion that, if the
environment satisfies Implementation-Defined conditions, will never engage in
Undefined Behavior.

A program that unconditionally output "Code is no good!" when given any
source text would qualify as an MSC implementation, though obviously of
such low quality as to be useless. Likewise an implementation that
imposed impossible requirements upon the execution environment (if an
implementation specifies that behavior is only defined between a temperature
of 24.999C and 25.001C, it could do anything it likes under almost any
practical real-world conditions). On the other hand, unlike the present
Standard for Conforming implementations, a standard for Minimally Safely
Conforming implementations would define a class of programs that they
could reliably identify as supporting or not supporting.
Francis Glassborow
2017-04-03 13:31:24 UTC
Permalink
Post by s***@casperkitty.com
Post by James R. Kuyper
Keep in mind that, because such declarations are currently allowed, a
lot of code has already been written to deliberately use such
declarations. All of that code would break if such a change were made.
If the Standard were to say that something is no longer required to be
treated as part of the language, but implementations may support it as an
optional extension, that would maintain compatibility with existing code.
Further, in cases where there would be an obvious way to adjust the code
to would work under *both* the old and new rules, and if the adjusted code
would by most measures be considered at least as good as the original,
that would be just about the least harmful form of incompatibility.
Further, in cases where a construct would be treated differently by different
implementations, having an alternative form for each treatment would make it
uint32_t x,y,z; // Assume values assigned somehow
int result = (x-y) > z;
the code would be legal on systems where "int" is 32 bits, and on those where
it is 64 bits, but it would have different defined meanings on the different
systems. If such code were in the "optionally supported" category, porting
to systems with different sizes of "int" could be facilitated by having a
compiler reject expressions that would have different defined meanings on
systems with different integer sizes.
Post by James R. Kuyper
The committee has strong policies in favor of maintaining backwards
compatibility. Changes that convert existing code with well-defined
behavior into code which has undefined behavior or different defined
behavior are approved only if the committee believes that they are very
well motivated (the committee's judgement on such matters is, of course,
often hotly contested). I doubt that your motivation for this change is
good enough for the committee to ever approve it.
Changes that would cause code to go from having defined behavior to having
Undefined Behavior were not regarded as breaking changes, since compiler
writers are presumed to have the judgment necessary to support the kinds
of code people will want to run on them.
Under C89, for example, the Common Initial Sequence guarantee defined
behaviors in some cases where it would not be defined under C99. Certain
kinds of vector-processing code benefit from being able to have distinct
"position" and "velocity" types which have the same members but are not
presumed to be alias-compatible. Compilers which are developed to exploit
that presumption may not be able to run code which relies upon the CIS
guarantees, but such code should continue to work just fine on any existing
compilers that aren't modified to break it.
Too many people forget that C is used in quite a number of situations
that require code to conform to the Standard. If we change the Standard
so that such code ceases to conform then the code has to be edited to
conform and then the code has to be revalidated. This is not a cheap
thing to do and is a potential source of new bugs in critical code.

Most programmers do not work in such demanding situations but those that
do understand the issues.

There is no requirement for anyone to use C but some work items do
specify that code be written in a Standard language and is compiled with
a conforming compiler. I know that does not absolutely guarantee that
the plane I am on does not fly into a mountain but ...
Francis
s***@gmail.com
2017-04-01 15:52:53 UTC
Permalink
– скриване на цитирания текст –
Post by James R. Kuyper
...
Post by s***@gmail.com
Post by James Kuyper
Post by s***@gmail.com
... However I feel like it
should be implemented rather as a simple text replacement than whole
type system change. Now I know that the preprocessor is another
monster but I doubt that implementing a check for direct array
declarator of incomplete type will be that expensive to the parser.
A key problem is that the rule as currently written refers to
"declarations", "parameters", and "arrays", none of which exist until
translation phase 7. Preprocessing takes place during translation phase
4, when all you have is an unanalyzed sequence of pre-processing tokens.
If something similar to the existing rule were re-written to be a simple
text replacement process similar to macro replacements, the rule would
be incapable of distinguishing based upon any of those concepts. It
would have to say something like "identifier [ ]" gets automatically
replaced by "(*identifier)". It couldn't do a context-sensitive
replacement, because the relevant context won't be parsed until
translation phase 7.
1. This is a file scope declaration of array with external linkage of
unspecified length. The actual length is determined by definition of the
extern int array[];
By changing this rule to be a simple text-replacement rule, that
extern int (*array);
struct counted_string{
size_t size;
char string[];
}
struct counted_string{
size_t size;
char *string;
}
Defining it as a text replacement would also allow it to be used in
contexts that have nothing to do with declarations, and would in fact be
syntax errors or constraint violations under the current rules, because
the text replace would take place before the syntax rule or constraint
can be applied. The
double *d = &pi;
d[] = 3.14;
would, by text replacement, become
double *d = &pi;
(*d) = 3.14;
which is perfectly legal.
I suspect, given the four statements that I responded to above, that you
don't have a firm grasp on all of the relevant jargon, and that you
therefore mean something different from what you've actually said. It
would help to clarify exactly what you mean, if you could provide a
wider range of examples which are permitted under the current rules,
which would be prohibited under your proposed change to the rules.
Have you read my last sentence: "Now I know that the preprocessor is another monster but I doubt that implementing a check for direct array declarator of incomplete type will be that expensive to the **parser**. "
Yes. The preceding discussion was in fact prompted by my reading of that
sentence, as well as by my failure to figure out what you meant by that
sentence. That's why I posted that discussion immediately after quoting
that sentence. That discussion was intended to provide a framework that
you could respond to by explaining in more detail what you actually
meant. Is there any particular reason why you failed to do so?
Post by s***@gmail.com
- I give you one last chance.
One last chance to do what?
To read my sentence :). Anyway. Well I meant we should allow only "sensible" declarations like:


int f(int [][2]), fa(int [][*][*]), fb(int [static 6]),

fc(int a[][2]), fd(int b[][*][*]), fe(int c[static 6]);

Which basically are declaring any function parameter as "incomplete array of" or any other array type if there is a `static` keyword inside the `[` and `]` of the array type derivation.

But not confusing and unnecessary ones like:

int f1(int [2]), f1(int [*]),

f1(int a[2]), f1(int b[*]);

typedef int tar[6];

int f2(tar), f2(tar a), f2(int [2]), f2(int b[2]);

Note that I've used both declarators and abstract declarators in the above examples.
s***@gmail.com
2017-04-01 15:58:05 UTC
Permalink
Note that the first examples should have been written rather like:

int f(int [][2]), fa(int [][*][*]), fb(int [static 6]),

f(int a[][2]), fa(int b[][*][*]), fb(int c[static 6]);

To be consistent with the other ones.
Loading...