8

As mentioned here, here and here a function (in c99 or newer) defined this way

void func(int ptr[static 1]){
    //do something with ptr, knowing that ptr != NULL
}

has one parameter (ptr) of type pointer to int and the compiler can assume that the function will never be called with null as argument. (e.g. the compiler can optimize null pointer checks away or warn if func is called with a nullpointer - and yes I know, that the compiler is not required to do any of that...)

C17 section 6.7.6.3 Function declarators (including prototypes) paragraph 7 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. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

In case of the definition above the value of ptr has to provide access to the first element of an array with at least 1 element. It is therefore clear that the argument can never be null.

What I'm wandering is, whether it is valid to call such a function with the address of an int that is not part of an array. E.g. is this (given the definition of func above) technically valid or is it undefined behavior:

int var = 5;
func(&var); 

I am aware that this will practically never be an issue, because no compiler I know of differentiates between a pointer to a member of an int array and a pointer to a local int variable. But given that a pointer in c (at least from the perspective of the standard) can be much more than just some integer with a special compile time type I wandered if there is some section in the standard, that makes this valid.

I do suspect, that it is actually not valid, as section 6.5.6 Additive operators paragraph 8 contains:

[...] If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. [...]

To me that sounds as if for any pointer that points to an array element adding 1 is a valid operation while it would be UB to add 1 to a pointer that points to a regular variable. That would mean, that there is indeed a difference between a pointer to an array element and a pointer to a normal variable, which would make the snippet above UB...

Section 6.5.6 Additive operators paragraph 7 contains:

For the purposes of these operators, a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.

As the paragraph begins with "for the purposes of these operators" I suspect that there can be a difference in other contexts?


tl;dr;

Is there some section of the standard, that specifies, that there is no difference between a pointer to a regular variable of type T and a pointer to the element of an array of length one (array of type T[1])?

T S
  • 1,656
  • 18
  • 26
  • 2
    You can pass the address of a scalar variable to a function that treats it as an array of length 1. – Tom Karzes Jul 02 '20 at 18:18
  • @TomKarzes Says who? :-) I don't suspect any problems in just doing so, but does the standard officially allow it? If so, which section of the standard? – T S Jul 02 '20 at 18:22
  • I think the best you'll get is 6.5.2.1/2 which defines array subscripting in terms of pointer dereferencing. In other words, arrays are just syntactic sugar. – user3386109 Jul 02 '20 at 18:23
  • Traditionally an array parameter in C was adjusted to a pointer. So if you declared a parameter as `int a[]`, it was adjusted to `int *a`. You can clearly pass the address of a scalar integer to a function as an `int *` parameter. I say traditionally because C now supports dynamic dimension information, but I believe the basic model is still the same. – Tom Karzes Jul 02 '20 at 18:27
  • The function's parameter type is *always* adjusted to a pointer type regardless of whether the object passed to it was an array or pointer type. It's same whether the argument has `[static X]` requirement or not. So the entire part on `[static 1]` in the question doesn't change anything. – P.P Jul 02 '20 at 18:38

2 Answers2

3

At face value, I think you have a point. We aren't really passing a pointer to the first element of an array. This may be UB if we consider the standard in a vacuum.

Other than the paragraph you quote in 6.5.6, there is no passage in the standard equating a single object to an array of one element. And there shouldn't be, since the two things are different. An array (of even one element) is implicitly converted to a pointer when appearing in most expressions. That's obviously not a property most object types posses.

The definition of the static keyword in [] mentions that the the pointer being passed, must be to the initial element of an array that contains at least a certain number of elements. There is another problem with the wording you cited, what about

int a[2];
func(a + 1);

Clearly the pointer being passed is not to the first element of an array. That is UB too if we take a literal interpretation of 6.7.6.3p7.

Putting the static keyword aside, when a function accepts a pointer to an object, whether the object is a member of an array (of any size) or not matters in only one context: pointer arithmetic.

In the absence of pointer arithmetic, there is no distinguishable difference in behavior when using a pointer to access an element of an array, or a standalone object.

I would argue that the intent behind 6.7.6.3p7 has pointer arithmetic in mind. And so the semantic being mentioned comes hand in hand with trying to do pointer arithmetic on the pointer being passed into the function.

The use of static 1 simply emerged naturally as useful idiom, and maybe wasn't the intent from get go. While the normative text may do with a slight correction, I think the intent behind it is clear. It isn't meant to be undefined behavior by the standard.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • If in the next few days nobody else suddenly answers with a quote that explicitly allows this, then I will accept your answer. Do you know, if there is any easy way to suggest changes to the standard (improved wording, that would explicitly allow this), like there is for c++ (https://isocpp.org/std/submit-a-proposal) – T S Jul 02 '20 at 20:00
  • For the vast majority of questions people ask here about whether X is defined, the answer would be that the authors of the Standard thought it sufficiently obvious that it should be that there was no need to actually say so, because they failed to imagine that "clever" compilers would go out of their way to exploit the Standard's failure to mandate the behavior. – supercat Jul 02 '20 at 21:54
  • @TS: The biggest thing I'd like to see the C and C++ Standards Committees do is publish a consensus opinion about the extent to which they are or are not intended to have jurisdiction over non-portable programs, especially whose purpose would most readily be accomplished by having an implementation specify how they would process some actions which aren't defined by the Standard. From what I can tell, both languages are caught in a catch-22 between committee members who insist that there's no need for the Standard to address such things since implementations can define them at their leisure... – supercat Jul 06 '20 at 19:31
  • ...if doing so would benefit their customers, and other people who insist that the Standard's failure to address such things implies a judgment that no programmers should ever need them. I can't think of any means of formally expressing that in a "proposal", but would suggest that if striking from the Standard some rule that exists to facilitate "optimization" would make it easy to accomplish some task using only defined behaviors, an implementation that is suitable for the task should allow programmers to accomplish it reliably without having to jump through hoops. – supercat Jul 06 '20 at 19:45
2

The authors of the Standard almost certainly intended that quality implementations would treat the value of a pointer to a non-array object in the same way as it would treat the value of a pointer to the first element of an array object of length 1. Had it merely said that a pointer to a non-array object was equivalent to a pointer to an array, however, that might have been misinterpreted as applying to all expressions that yield pointer values. This could cause problems given e.g. char a[1],*p=a;, because the expressions a and p both yield pointers of type char* with the same value, but sizeof p and sizeof a would likely yield different values.

The language was in wide use before the Standard was written, and it was hardly uncommon for programs to rely upon such behavior. Implementations that make a bona fide effort to behave in a fashion consistent with the Standard Committee's intentions as documented in the published Rationale document should thus be expected to process such code meaningfully without regard for whether a pedantic reading of the Standard would require it. Implementations that do not make such efforts, however, should not be trusted to process such code meaningfully.

supercat
  • 77,689
  • 9
  • 166
  • 211