Which C rules allow to use in function declarator an identifier, which was previously defined as a type?

Viewed 66

Consider this code (t0.c):

typedef int T;
void f(int T);

Invocations:

gcc -std=c11 -Wall -Wextra -pedantic -c t0.c
<nothing>

clang -std=c11 -Wall -Wextra -pedantic -c t0.c
<nothing>

cl /std:c11 /Za /c t0.c
t0.c(2): warning C4224: nonstandard extension used: formal parameter 'T' was previously defined as a type

Questions:

  1. Which C rules allow to use in function declarator an identifier, which was previously defined as a type?
  2. If msvc says nonstandard extension, then why standard conforming gcc / clang does not say anything?
3 Answers

1

  1. Which C rule allows to use in function declarator an identifier, which was previously defined as a type?

First, a typedef name is an identifier. C 2018 6.2.1 1 says:

An identifier can denote an object; a function; a tag or a member of a structure, union, or enumeration; a typedef name; a label name; a macro name; or a macro parameter…

The same paragraph says we can use the same identifier to denote different entities:

… The same identifier can denote different entities at different points in the program…

Paragraph 4 tells us the T declared inside the function prototype hides the earlier T:

… Within the inner scope, the identifier designates the entity declared in the inner scope; the entity declared in the outer scope is hidden (and not visible) within the inner scope.

2

  1. If msvc says nonstandard extension, then why standard conforming gcc / clang does not say anything?

GCC and Clang do not say anything because the code is fine according to the C standard, and their implementors have not chosen to issue any warning.

MSVC is allowed by the C standard to report diagnostics beyond what the standard requires, as all C implementations are, but it is incorrect to allege it is a non-standard extension.

C17 6.2.1/2:

Different entities designated by the same identifier either have different scopes, or are in different name spaces. There are four kinds of scopes: function, file, block, and function prototype.

C17 6.2.1/4:

Every other identifier has scope determined by the placement of its declaration (in a declarator or type specifier). If the declarator or type specifier that declares the identifier appears outside of any block or list of parameters, the identifier has file scope, which terminates at the end of the translation unit.
If the declarator or type specifier that declares the identifier appears inside a block or within the list of parameter declarations in a function definition, the identifier has block scope, which terminates at the end of the associated block.
If the declarator or type specifier that declares the identifier appears within the list of parameter declarations in a function prototype (not part of a function definition), the identifier has function prototype scope, which terminates at the end of the function declarator.

Your typedef resides at file scope but the parameter at function prototype scope. Also see 6.2.3 regarding namespaces of identifiers. These are "ordinary identifiers" so the inner scope one always takes precedence of the outer scope one.

If msvc says nonstandard extension, then why standard conforming gcc / clang does not say anything?

Supposedly because MSVC is bad, non-compliant and known to give incorrect diagnostics.

The code you show is equivalent to

typedef int T;
void f(int);

Maybe you wanted

typedef int T;
void f(int T) {
    (void)T; // ignore unused parameter
}

for which gcc ... -Wshadow ... and clang ... -Weverything ... emit a diagnostic (gcc v6.3.0; clang v3.8.1).

Related