When trying to take advantage of the C99 function prototype syntax specifying non null pointers for function arguments, I came across some inconsistent behavior between clang and gcc:
A function can be declared and defined to receive a non-null pointer to an array of a minimum size. For example:
char *mystrcpy(char dest[restrict static 1], char const src[restrict static 1]);
declares function mystrcpy
to take non-null restricted pointers to char
arrays.
This definition is stricter than the standard definition of strcpy
that uses a more classic form:
char *strcpy(char restrict *dest, const char restrict *src);
Where the C compiler is given no information about the constraint ont the arguments to be non-null.
I wrote a test program to verify the compatibility of these 2 prototypes and was surprised to discover that they are indeed compatible although more information is carried in the first than the second. Further surprising were these facts:
strcpy
receiving null arguments.strcpy
receiving null arguments, not not about mystrcpy
, in spite of its unambiguous definition.strcpy
or mystrcpy
to function pointers defined with both syntaxes did not cause any warning.My question is: are these observations consistent with the C Standard or are gcc and/or clang incorrect in their implementation of C99's
static
keyword inside the[]
of a function argument?
Here is the code:
#include <stdio.h>
#include <string.h>
static char *mystrcpy(char dest[restrict static 1], char const src[restrict static 1]) {
char *p = dest;
while ((*p++ = *src++) != '\0')
continue;
return dest;
}
static char *(*f1)(char *dest, const char *src) = strcpy;
static char *(*f2)(char *dest, const char *src) = mystrcpy;
static char *(*f3)(char dest[restrict static 1], char const src[restrict static 1]) = strcpy;
static char *(*f4)(char dest[restrict static 1], char const src[restrict static 1]) = mystrcpy;
int main() {
char a[100];
strcpy(a, "a");
strcpy(a, "");
strcpy(a, NULL);
strcpy(a, a);
strcpy(NULL, a);
strcpy(NULL, NULL);
mystrcpy(a, "a");
mystrcpy(a, "");
mystrcpy(a, NULL);
mystrcpy(a, a);
mystrcpy(NULL, a);
mystrcpy(NULL, NULL);
f1(a, "a");
f1(a, "");
f1(a, NULL);
f1(a, a);
f1(NULL, a);
f1(NULL, NULL);
f2(a, "a");
f2(a, "");
f2(a, NULL);
f2(a, a);
f2(NULL, a);
f2(NULL, NULL);
f3(a, "a");
f3(a, "");
f3(a, NULL);
f3(a, a);
f3(NULL, a);
f3(NULL, NULL);
f4(a, "a");
f4(a, "");
f4(a, NULL);
f4(a, a);
f4(NULL, a);
f4(NULL, NULL);
return 0;
}
gcc output: it only complains about the direct calls to strcpy
with NULL
arguments.
$ gcc -O2 -std=c99 -Wall -Wextra -W -o sc sc.c sc.c: In function 'main': sc.c:22:5: warning: null argument where non-null required (argument 2) [-Wnonnull] sc.c:22:5: warning: null argument where non-null required (argument 2) [-Wnonnull] sc.c:24:5: warning: null argument where non-null required (argument 1) [-Wnonnull] sc.c:24:5: warning: null argument where non-null required (argument 1) [-Wnonnull] sc.c:25:5: warning: null argument where non-null required (argument 1) [-Wnonnull] sc.c:25:5: warning: null argument where non-null required (argument 2) [-Wnonnull] sc.c:25:5: warning: null argument where non-null required (argument 1) [-Wnonnull] sc.c:25:5: warning: null argument where non-null required (argument 2) [-Wnonnull]
clang's output: only complains about the direct calls to mystrcpy
with NULL
arguments.
$ clang -Weverything -o sc sc.c sc.c:29:5: warning: null passed to a callee that requires a non-null argument [-Wnonnull] mystrcpy(a, NULL); ^ ~~~~ sc.c:4:64: note: callee declares array parameter as static here static char *mystrcpy(char dest[restrict static 1], char const src[restrict static 1]) { ^ ~~~~~~~~~~~~~~~~~~~ sc.c:31:5: warning: null passed to a callee that requires a non-null argument [-Wnonnull] mystrcpy(NULL, a); ^ ~~~~ sc.c:4:28: note: callee declares array parameter as static here static char *mystrcpy(char dest[restrict static 1], char const src[restrict static 1]) { ^ ~~~~~~~~~~~~~~~~~~~ sc.c:32:5: warning: null passed to a callee that requires a non-null argument [-Wnonnull] mystrcpy(NULL, NULL); ^ ~~~~ sc.c:4:28: note: callee declares array parameter as static here static char *mystrcpy(char dest[restrict static 1], char const src[restrict static 1]) { ^ ~~~~~~~~~~~~~~~~~~~ sc.c:32:5: warning: null passed to a callee that requires a non-null argument [-Wnonnull] mystrcpy(NULL, NULL); ^ ~~~~ sc.c:4:64: note: callee declares array parameter as static here static char *mystrcpy(char dest[restrict static 1], char const src[restrict static 1]) { ^ ~~~~~~~~~~~~~~~~~~~ 4 warnings generated.
A more recent version of gcc also complains about passing the same pointer for 2 restrict qualified arguments, but still no support for the static
minimum length specifier (see Godbolt session):
sc.c:30:14: warning: passing argument 1 to 'restrict'-qualified parameter aliases with argument 2 [-Wrestrict] 30 | mystrcpy(a, a); | ^ ~ sc.c:51:8: warning: passing argument 1 to 'restrict'-qualified parameter aliases with argument 2 [-Wrestrict] 51 | f3(a, a); | ^ ~ sc.c:58:8: warning: passing argument 1 to 'restrict'-qualified parameter aliases with argument 2 [-Wrestrict] 58 | f4(a, a); | ^ ~ sc.c:37:5: warning: 'strcpy' source argument is the same as destination [-Wrestrict] 37 | f1(a, a); | ^~~~~~~~
Based on this answer, the compiler doesn't have to diagnose the call to such function and verify that the parameters meet the static
qualifier.
Note that the C Standard does not require the compiler to diagnose when a call to the function does not meet these requirements (i.e. it is silent undefined behaviour).
EDIT:
Based on the Standard C99:
6.7.5.3 Function declarators (including prototypes)
...
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.
...
J.2 Undefined behavior
...
— A declaration of an array parameter includes the keyword static within the [ and ] and the corresponding argument does not provide access to the first element of an array with at least the specified number of elements (6.7.5.3).
My question is: are these observations consistent with the C Standard or are gcc and/or clang incorrect in their implementation of C99's
static
keyword inside the[]
of a function argument?
The observations are consistent with the standard.
With respect to diagnostics, the standard provides that
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.
, but that is part of the semantic description, not a constraint, so there is no requirement that implementations produce diagnostics about violations. Code that violates that provision has undefined behavior, of course, but that's a separate matter.
And even if there were a constraint violation, a conforming implementation is not obligated to reject the code; the only requirement on the implementation in such a case is that it emit a diagnostic message.
As for function-pointer type compatibility, the standard specifies that
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.
static
is not among the type qualifiers (they are zero or more of const
, restrict
, and volatile
), so its appearance in the function signature does not serve to alter the function's type. Thus, pointers to these two functions
char *mystrcpy(char dest[restrict static 1], char const src[restrict static 1]);
[...]
char *strcpy(char restrict *dest, const char restrict *src);
indeed do have compatible (in fact the same) type. The static 1
simply does not factor in to that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With