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:
- with all warnings enabled, clang does not complain about
strcpy
receiving null arguments. - gcc did complain about
strcpy
receiving null arguments, not not aboutmystrcpy
, in spite of its unambiguous definition. - assigning
strcpy
ormystrcpy
to function pointers defined with both syntaxes did not cause any warning. - passing null pointers to an indirect call through a function pointer did not trigger a warning in clang where the direct call did.
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); | ^~~~~~~~