1

Expanding the syntax of "declaration-specifiers" from the C standard I get a syntax that permits many combinations of specifiers that are semantically illogical. Has anyone written a more precise syntax that permits fewer contradictory combinations? It appears the unordered nature of the syntax (presumable intended so the programmer is not burdened with having to remember an order of specifiers) makes this complicated. It would help if there some notation for specifying "any of a choice of specifiers but only one occurance of each of a choice is allowed" (that isn't particularly well worded). Or am I barking up the wrong tree? if, for example, there is a nice concise set of semantic rules that specify which combinations are (not) permitted together. From my interpretation of the C standard the syntax is

declaration-specifiers: (storage-class-specifier|type-specifier|type-qualifier|function-specifier|alignment-specifier)+

storage-class-specifier: 'typedef'|'extern'|'static'|'_Thread_local'|'auto'|'register'

type-specifier: 'void'|'char'|'short'|'int'|'long'|'float'|'double'|'signed'|'unsigned'|'_Bool'|'_Complex' | '_Atomic' '(' type-name ')' | struct-or-union-specifier | enum-specifier | typedef-name

type-qualifier: 'const'|'restrict'|'volatile'|'_Atomic'

function-specifier: 'inline' | '_Noreturn'

alignment-specifier: '_Alignas' '(' type-name | constant-expression ')'

Kev Sepia
  • 13
  • 2
  • Give an example or three of what you consider illogical. You'll probably be surprised about how many of them are valid. For example, `int unsigned long typedef long silly;` should be OK. – Jonathan Leffler Apr 06 '20 at 13:09
  • I think this answer of one of my questions could be very helpful for your concern as it was for me: https://stackoverflow.com/a/60439186/12139179 – RobertS supports Monica Cellio Apr 06 '20 at 13:11
  • @JonathanLeffler Not knowing what is allowed is the root of my problem. I would guess it would be illegal to have multiple alignment-specifiers; signed/unsigned bool, stuct, enum; inline/_noreturn on non-functional types; 'restrict' outside parameter list; But I don't want to guess, I want to know. It seems from saying that the contexts within which the specifiers appear are significant in restricting what is meaningful, so perhaps the scope of my question is too narrow. – Kev Sepia Apr 06 '20 at 13:27
  • See also C11 [§6.11.5 Storage class specifiers](http://port70.net/~nsz/c/c11/n1570.html#6.11.5): _The placement of a storage-class specifier other than at the beginning of the declaration specifiers in a declaration is an obsolescent feature._ – Jonathan Leffler Apr 06 '20 at 13:38
  • What's allowed is what the grammar specifies — and it is anarchic. What's sensible is to use the sequence ` ` before the ``. Within the ``, you sequence the words for `int` types according to ` ` where you can't have all three optional parts empty. The other types are similar but simpler — the optional length qualifier precedes the base type. Note that some 'cv-qualifiers' are associated with the declarator: eg `const int * const ptr = &x;`. – Jonathan Leffler Apr 06 '20 at 14:07
  • It's also often a good idea to limit the `` to a single declarator. – Jonathan Leffler Apr 06 '20 at 14:07
  • This is one of the weirder parts of the language... this declaration is perfectly fine C `static int const signed const long const long const volatile volatile* const restrict volatile const restrict volatile a;`... – Lundin Apr 06 '20 at 14:44
  • @Lundin I thought I was beginning to understand. Redundancy through repetition I'm ok with but how are 'const' and 'volatile' allowed together? – Kev Sepia Apr 06 '20 at 15:06
  • @Lundin I thought about it, and think I get what 'const' and 'volatile' together mean. It's saying the value of the object might be changed by something outside the program, but it is not the programs intention to modify the value of an object of this type (but the program might modify the value of the object if it is accessed using a different type). Is that right? – Kev Sepia Apr 06 '20 at 16:22
  • @KevSepia It's quite common to combine the two, since they have completely different meanings. `const` simply means read-only and `volatile` means that the value can change at any time so that it can't get optimized. Real life examples include read-only hardware registers, variables allocated in EEPROM, pointers to registers passed to functions that shouldn't modify the actual register (const correctness on volatile variables) etc etc. – Lundin Apr 07 '20 at 06:35
  • @JonathanLeffler regarding storage class specifiers at the beginning. gcc -std=c11 -pedantic -Wall -Wextra (-Wextra implies -Wold-style-declaration) gives correct warnings on examples I've tried, except it does not warn about "inline static void f() {}" Should it? – Kev Sepia Apr 07 '20 at 08:38
  • @Lundin Thanks. I have to stop automatically thinking 'const' means constant. – Kev Sepia Apr 07 '20 at 09:20
  • @KevSepia "it does not warn about "inline static void f() {}" Yeah as far as I can tell, function specifiers are no special case and the storage class specifier should be placed first. So that could be a compiler bug. – Lundin Apr 07 '20 at 11:00
  • Speaking of which, function specifiers may also appear multiple times (because why not...) `static inline inline inline void f(void);` is valid C. – Lundin Apr 07 '20 at 11:00
  • @Lundin hmmm, I suppose the compiler saves a few precious nanoseconds avoiding a 'bit or' and a comparison to detect such needless repetition, and without those extra instructions the compiler object code is a few precious bytes shorter, brevity being the soul of wit and all that ;-) – Kev Sepia Apr 07 '20 at 11:45

2 Answers2

1

There are separate constraints in the language definition that specify which combinations of declaration specifiers are legal; it is not specified within the grammar itself. For example:

6.7.2 Type specifiers
...
Constraints

2 At least one type specifier shall be given in the declaration specifiers in each declaration, and in the specifier-qualifier list in each struct declaration and type name. Each list of type specifiers shall be one of the following multisets (delimited by commas, when there is more than one multiset per item); the type specifiers may occur in any order, possibly intermixed with the other declaration specifiers.

— void
— char
— signed char
— unsigned char
— short, signed short, short int, or signed short int
— unsigned short, or unsigned short int
— int, signed, or signed int
— unsigned, or unsigned int
— long, signed long, long int, or signed long int
— unsigned long, or unsigned long int
— long long, signed long long, long long int, or signed long long int
— unsigned long long, or unsigned long long int
— float
— double
— long double
— _Bool
— float _Complex
— double _Complex
— long double _Complex
— atomic type specifier
— struct or union specifier
— enum specifier
— typedef name

C 2011 Online Draft

While all that probably could be encoded in the grammar, it would be cumbersome. Making it a separate constraint makes life a little easier, I think (and also makes it easier to introduce new combinations, or deprecate old ones).

Community
  • 1
  • 1
John Bode
  • 119,563
  • 19
  • 122
  • 198
  • @John_Bode Thanks. I'm beginning to get the idea. This goes some of the way to what I'm after. Interestingly sometimes redundancy is allowed (e.g. signed int is ok) and sometimes not (e.g. no signed double). Typedefs are not "looked into" so you can't have typedef int t; signed t foo; i.e. a syntactic restriction of what would be semantically ok. Are there similar mutisets for type-qualifier and the storage-class-specifier? function-specifier is obvious. And then perhaps some rules for combining these mutisets? It would be nice to have them all in one place for groking the combinations. – Kev Sepia Apr 06 '20 at 14:23
  • so looking at the constraints on storage-class-specifier I get the multisets: typedef; extern; extern _Thread_local; static; static _Thread_local; _Thread_local; auto; register. Is that right? – Kev Sepia Apr 06 '20 at 14:43
  • @KevSepia Looks correct. C17 6.7.1/2 "At most, one storage-class specifier may be given in the declaration specifiers in a declaration, except that _Thread_local may appear with static or extern" – Lundin Apr 06 '20 at 14:47
  • 1
    @KepSepia: `const volatile` is fine. A read-only clock register is `const` to the C program and changes by means outside the C implementation, so it is `volatile`. Also, merely reading a `const volatile` object may cause some action. For example, reading a byte from a device may cause the device to get the next byte from its data source. – Eric Postpischil Apr 06 '20 at 16:27
0

The C standard implies no rule about the order how to implement declaration specifiers, storage-class specifiers, type specifiers, datatype specifiers or alignment specifiers inside of the declaration of a certain object.

You can intermix them arbitrary.

Quote from the ISO/IEC 9899:2018, Section 6.7.2/2

"At least one type specifier shall be given in the declaration specifiers in each declaration, and in the specifier-qualifier list in each struct declaration and type name. Each list of type specifiers shall be one of the following multisets (delimited by commas, when there is more than one multiset per item); the type specifiers may occur in any order, possibly intermixed with the other declaration specifiers."

The only constraints are that at least one type specifier and at least one identifier has to be provided and the identifier has to stand on the right hand side of the declaration.

By the way there is another thing to consider, although it is syntactically correct yet. It is obsolete to place a storage-class specifier anywhere else than the first position.

Quote from the ISO/IEC 9899:2011, Section 6.11.5 - Storage class specifiers:

"The placement of a storage-class specifier other than at the beginning of the declaration specifiers in a declaration is an obsolescent feature."

Thus,

static const long long int a;

can be written as:

static long int const long a;

or

static long const long int a;

or

static int const long long a;

But to keep your code readable and clear for future readers, you should use the common convention.

  • Actually, placing the storage class specifier anywhere else than at the beginning of the declaration is listed as an obsolete feature (since C99 6.11.5 and still to this day C17 6.11.5). Meaning that `static` should always be first, the rest of them can be placed in any haywire order. – Lundin Apr 06 '20 at 14:39
  • @Lundin Yes, thank you Lundin, Jonathan Leffler also said that in the comments already but I was to be honest too busy to implement it. I took it into the answer. – RobertS supports Monica Cellio Apr 06 '20 at 14:52