Declaring a function with different function specifiers

114 Views Asked by At

Consider this C code:

_Noreturn void exit(int status);
void exit(int status);

int main(void) {
    exit(0);
}

It declares the exit function twice, once with the _Noreturn function specifier, and once without. This seems like it should be Undefined Behavior, but I can't find anything concrete in the standard that says so, and I don't get any compiler warnings about it no matter how high I set my warning level. Is it actually UB, or is it okay? And since the C standard says exit is _Noreturn, if I only had the declaration that doesn't have that, would that be UB or okay?

3

There are 3 best solutions below

0
Eric Postpischil On BEST ANSWER

Is it actually UB, or is it okay?

Presumably you are concerned about whether these declarations violate some rule regarding declaring identifiers twice, notably compatibility. C 2018 6.2.7 2 says:

All declarations that refer to the same object or function shall have compatible type; otherwise, the behavior is undefined.

The rules for compatibility of function types are specified in C 2018 6.7.6.3 15. Briefly, they say:

  • The return types shall be compatible.
  • If they both have parameter type lists, the lists have the same number of parameters, both have ... or not, and the corresponding parameter types are compatible (after removing qualifiers from the parameter types and adjusting function and array types to pointers).
  • Otherwise, the parameter parts of the declarations have to satisfy various requirements that do not concern us here.

These rules do not say the function specifiers have to satisfy any requirements. So the two declarations you show declare exit with compatible types and therefore satisfy 6.2.7 2.

And since the C standard says exit is _Noreturn, if I only had the declaration that doesn't have that, would that be UB or okay?

It does not appear to violate any rule in C 2018. As Jonathan Leffler’s answer notes, this changes in the expected C 2023 standard.

0
Jonathan Leffler On

What the standards say

The C23 standard (or, at least, the N3054 draft of it) is quite clear:

§6.7.12.6 The noreturn and _Noreturn attributes

Description

When _Noreturn is used as an attribute token (instead of a function specifier), the constraints and semantics are identical to that of the noreturn attribute token. Use of _Noreturn as an attribute token is an obsolescent feature187).

Constraints

The noreturn attribute shall be applied to the identifier in a function declaration. No attribute argument clause shall be present.

Semantics

The first declaration of a function shall specify the noreturn attribute if any declaration of that function specifies the noreturn attribute. If a function is declared with the noreturn attribute in one translation unit and the same function is declared without the noreturn attribute in another translation unit, the behavior is undefined.

187) [[_Noreturn]] and [[noreturn]] are equivalent attributes to support code that includes <stdnoreturn.h>, because that header defines noreturn as a macro that expands to _Noreturn.

The 'attribute token' refers to the new notation in C23 like [[noreturn]], as opposed to the simpler _Noreturn function specifier in C11 and C18. Attribute tokens were not a part of C11 or C18.

The C11 standard was not as clear about this (see §6.7.4 Function specifiers, which does not mention such restrictions AFAIK).

The C23 standard §6.7.4 Function specifiers says:

A function declared with a _Noreturn function specifier shall not return to its caller. The attribute [[noreturn]] provides similar semantics. The _Noreturn function specifier is an obsolescent feature (6.7.12.6).

Also, §6.7.12 Atributes ¶2 of C23 says:

Support for any of the standard attributes specified in this document is implementation-defined and optional. For an attribute token (including an attribute prefixed token) not specified in this document, the behavior is implementation-defined. Any attribute token that is not supported by the implementation is ignored.

Further, §6.7.12.1 General ¶3 of C23 says:

A strictly conforming program using a standard attribute remains strictly conforming in the absence of that attribute.185)

185) Standard attributes specified by this document can be parsed but ignored by an implementation without changing the semantics of a correct program; the same is not true for attributes not specified by this document.

The C23 standard contains many rules that I have not mentioned (section 6.7.12 covers pages 142-151). To see the details, get the draft (or the published standard if it is available).

Applying the rules

Under the rules of C23, the code in the question is valid:

_Noreturn void exit(int status);
void exit(int status);

However, were the lines reversed, it would probably be invalid (assuming no other declarations of exit() were present before these lines in the translation unit) because of the "first declaration" semantic. If the _Noreturn was replaced by [[noreturn]] (or any legitimate variant spelling — there are alternatives), then the reverse sequence would definitely violate the "first declaration" semantic rule.

0
John Bollinger On

Through C17

It declares the exit function twice, once with the _Noreturn function specifier, and once without. This seems like it should be Undefined Behavior, but I can't find anything concrete in the standard that says so

The specifications for function specifiers have nothing to say about the matter, so the primary consideration seems to be that

All declarations in the same scope that refer to the same object or function shall specify compatible types.

(C17 6.7/4)

And what does it mean for two function types to be compatible? C17 paragraph 6.7.6.3/15 explains:

For two function types to be compatible, both shall specify compatible return types.

Check.

Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types.

Check.

[ various provisions not applying to the case in question ]

Moot.

So it seems that the two declarations do declare compatible types.

But what about the discrepancy in the use of the _Noreturn specifier, you ask? The concept of composite type comes into play here:

A composite type can be constructed from two types that are compatible; it is a type that is compatible with both of the two types and satisfies the following conditions:

[...]

  • If both types are function types with parameter type lists, the type of each parameter in the composite parameter type list is the composite type of the corresponding parameters.

The general idea is that the attributes of the type of an identifier can be specified jointly by multiple declarations that are not all identical to each other. It may be more familiar to you with the static storage-class specifier, the inline function specifier, or function declarations differing in whether they provide prototypes.

I don't get any compiler warnings about it no matter how high I set my warning level. Is it actually UB, or is it okay?

And since the C standard says exit is _Noreturn, if I only had the declaration that doesn't have that, would that be UB or okay?

Yes, because adding or removing _Noreturn does not affect type compatibility.

In C23

... it is more nuanced. As far as I can tell, _Noreturn still does not impact type compatibility, but there are new rules specific to it (via the noreturn attribute) that come into play, as described in @JonathanLeffler's answer.