ISO/ IEC JTC1/SC22/WG14 N861

SC22/WG14 N861 1998-11-03

               Issues with CD2 (FCD1)
                Clive Feather
               clive@demon.net


The following 13 items represent further issues with CD2. They are in order
of location within the CD.


========
[Item 01]
Category: Inconsistency
Committee Draft subsection: 6.2.5, 6.5.3.4, 6.7

Title: Issues with prototypes and completeness.

Detailed description:

6.2.5p23 says "An array type of unknown size is an incomplete type". Is the
type "int [*]" (which can only occur within a prototype) complete or
incomplete ?

If it is complete, then what is its size ? This can occur in the construct
    int f (int a [sizeof (int [*][*])]);

It it is incomplete, then the type "int [*][*]" is not permitted, which is
clearly wrong.

Now consider the prototype:

    int g (int a []);

The parameter clearly has an incomplete type, but since a parameter is an
object (see 3.16) this is forbidden by 6.7p7:

    If an identifier for an object is declared with no linkage, the type
    for the object shall be complete by the end of its declarator, or by
    the end of its init-declarator if it has an initializer.

This is also clearly not what was intended.

One way to fix the first item would be to change 6.5.3.4p1 to read:

    [#1]  The  sizeof  operator  shall  not  be  applied  to  an
    expression that has function type or an incomplete type,  to
 || an array type with unspecified size 72a), to
    the  parenthesized name of such a type, or to an lvalue that
    designates a bit-field object.

 || 72a) An array type with unspecified size occurs in function
 || prototypes when the notation [*] is used, as in "int [*][5][*]".

One way to fix the second item would be to change 6.7p7 to read:

    If an identifier for an object is declared with no linkage, the type
    for the object shall be complete by the end of its declarator, or by
    the end of its init-declarator if it has an initializer;
 || in the case of function arguments (including in prototypes) this shall
 || be after making the adjustments of 6.7.5.3 (from array and function
 || types to pointer types).


========
[Item 02]
Category: Normative change to existing feature retaining the original intent
Committee Draft subsection: 6.2.5, 6.7

Title: Problems with flexible array members

Detailed description:

Sometime after CD1 the following wording was added to 6.2.5p23:

    A structure type containing a flexible array member is an
    incomplete type that cannot be completed.

Presumably this was done to eliminate some conceptual problems with
structures that contain such members. However, this change makes almost all
use of such structures forbidden, because it is no longer possible to take
their size, and it is unclear what other operations are valid. This was
also not the intent behind the original proposal.

On the other hand, if such a structure is a complete type, there are a
number of issues to be defined, such as what happens when the structure is
copied or initialized. These need to be addressed.

The wording defining flexible array members is in 6.7.2.1p15:

    [#15] As a special case, the last  element  of  a  structure
    with more than one named member may have an incomplete array
    type.  This is called a flexible array member, and the  size
    of  the  structure  shall be equal to the offset of the last
    element of an otherwise identical  structure  that  replaces
    the  flexible  array  member  with  an  array of unspecified
    length.95)  When an lvalue whose type is a structure with  a
    flexible  array  member  is  used  to  access  an object, it
    behaves as if that member were  replaced  with  the  longest
    array,  with  the same element type, that would not make the
    structure larger than the object being accessed; the  offset
    of the array shall remain that of the flexible array member,
    even if this would  differ  from  that  of  the  replacement
    array.   If  this  array  would  have  no  elements, then it
    behaves as if it  had  one  element,  but  the  behavior  is
    undefined  if  any attempt is made to access that element or
    to generate a pointer one past it.

A solution to the problem is to leave the structure as complete but have
the flexible member ignored in most contexts. To do this, delete the last
sentence of 6.2.5p23, and change 6.7.2.1p15 as follows:
 
    [#15] As a special case, the last  element  of  a  structure
    with more than one named member may have an incomplete array
 || type.  This is called a flexible array member. With two
 || exceptions the flexible array member is ignored. Firstly, the
 || size of the structure shall be equal to the offset of the last
    element of an otherwise identical  structure  that  replaces
    the  flexible  array  member  with  an  array of unspecified
    length.95) Secondly, when the . or -> operator has a left
 || operand which is, or is a pointer to, a structure with a flexible
 || array member and the right operand names that member, it
    behaves as if that member were  replaced  with  the  longest
    array,  with  the same element type, that would not make the
    structure larger than the object being accessed; the  offset
    of the array shall remain that of the flexible array member,
    even if this would  differ  from  that  of  the  replacement
    array.   If  this  array  would  have  no  elements, then it
    behaves as if it  had  one  element,  but  the  behavior  is
    undefined  if  any attempt is made to access that element or
    to generate a pointer one past it.

Finally, add further example text after 6.7.2.1p18:

    The assignment:

        *s1 = *s2;

    only copies the member n, and not any of the array elements.
    Similarly:

        struct s  t1 = { 0 };          // valid
        struct s  t2 = { 2 };          // valid
        struct ss tt = { 1, { 4.2 }};  // valid
        struct s  t3 = { 1, { 4.2 }};  // error; there is nothing
                                       // for the 42 to initialize

        t1.n = 4;                      // valid
        t1.d [0] = 4.2;                // undefined behavior


========
[Item 03]
Category: Inconsistency
Committee Draft subsection: 6.2.5, 6.7.2.2

Title: Circular definition of enumerated types

Detailed description:

6.7.2.2 para 4 says:

    Each enumerated type shall be compatible with an integer type. 

However, 6.2.5 para 17 says:

    The type char, the signed and unsigned integer types,
    and the enumerated types are collectively called integer types.

Thus we have a circular definition. To fix this, change the former to one
of:

    Each enumerated type shall be compatible with a signed or unsigned
    integer type. 

or:

    Each enumerated type shall be compatible with a standard integer type
    or an extended integer type. 


========
[Item 04]
Category: Clarification
Committee Draft subsection: 6.2.6.2

Title: Clarify aspects of negative zeros and related situations

Detailed description:

Subclause 6.2.6.1p2 makes it clear that there are only three permitted
representations for signed integers - two's complement, one's complement,
and sign-and-magnitude. It is reported, however, that certain
implementations have problems with the "minus zero" representation;
furthermore, because signed and unsigned integer types have the same
representation over the common value range, it is useful to know when a
minus zero can appear.

The suggested change is to alter the last part of this paragraph:

    If the sign bit is one, then the value shall be modified in one of the
    following ways:
    -- the corresponding value with sign bit 0 is negated;
    -- the sign bit has the value -2N;
    -- the sign bit has the value 1-2N.

to:
 
    If the sign bit is one, then the value shall be modified in one of the
    following ways:
    -- the corresponding value with sign bit 0 is negated (/sign and
       magnitude/);
    -- the sign bit has the value -2N (/two's complement/);
    -- the sign bit has the value 1-2N (/one's complement/).
    The implementation shall document which shall apply, and whether the
    value with sign bit 1 and all value bits 0 (for the first two), or
    with sign bit and all value bits 1 (for one's complement) is a trap
    representation or a normal value. In the case of sign and magnitude
    and one's complement, if this representation is a normal value it is
    called a /negative zero/.

    If the implementation supports negative zeros, then they shall only be
    generated by:
    - the & | ^ ~ << and >> operators with appropriate arguments;
    - the + - * / and % operators where one argument is a negative zero
      and the result is zero;
    - compound assignment operators based on the above cases.
    It is unspecified if these cases actually generate negative zero or
    normal zero, and whether a negative zero becomes a normal zero or
    remains a negative zero when stored in an object.

    If the implementation does not support negative zeros, the behavior
    of an & | ^ ~ << or >> operator with appropriate arguments is undefined.


========
[Item 05]
Category: Normative change to existing feature retaining the original intent
Committee Draft subsection: 6.3.2.3

Title: Null pointer constants should be castable to pointer types

Detailed description:

6.3.2.3p3 says that:

    If a null pointer constant is assigned
    to or compared for equality to a pointer,  the  constant  is
    converted to a pointer of that type.  Such a pointer, called
    a null pointer,

However, this doesn't cover cases such as:

    (char *) 0

which is neither an assignment or a comparison. Therefore this is not a
null pointer constant, but rather an implementation-defined conversion from
an integer to a pointer. This is clearly an oversight and should be fixed.

Either change:

    If a null pointer constant is assigned
    to or compared for equality to a pointer,  the  constant  is
    converted to a pointer of that type.  Such a pointer, called
    a null pointer,  is  guaranteed  to  compare  unequal  to  a
    pointer to any object or function.

to:

    If a null pointer constant is assigned
    to or compared for equality to a pointer,  the  constant  is
    converted to a pointer of that type. When a null pointer
    constant is converted to a pointer, the result (called a /null
    pointer/) is guaranteed to compare unequal to a pointer to any
    object or function.

or change:

    If a null pointer constant is assigned
    to or compared for equality to a pointer,  the  constant  is
    converted to a pointer of that type.  Such a pointer, called
    a null pointer,  is  guaranteed  to  compare  unequal  to  a
    pointer to any object or function.

    [#4]  Conversion  of  a null pointer to another pointer type
    yields a null pointer of that type.  Any two  null  pointers
    shall compare equal.

to:

    If a null pointer constant is assigned
    to or compared for equality to a pointer,  the  constant  is
    converted to a pointer of that type.

    A /null pointer/ is a special value of any given pointer type
    that is guaranteed to compare unequal to a pointer to any
    object or function. Conversion of a null pointer constant to a
    pointer type, or of a null pointer to another pointer type,
    yields a null pointer of that type.  Any two null pointers
    shall compare equal.


========
[Item 06]
Category: Inconsistency
Committee Draft subsection: 6.4

Title: UCNs as preprocessing-tokens

Detailed description:

In 6.4 the syntax for "preprocessing-token" includes:
    identifier
    each universal-character-name that cannot be one of the above

In 6.4.2.1 the syntax for "identifier" includes:
    identifier:
        identifier-nondigit
        identifier identifier-nondigit
        identifier digit

    identifier-nondigit:
        nondigit
        universal-character-name
        other implementation-defined characters

Therefore a universal-character-name is always a valid identifier
preprocessing token, and so the second alternative can never apply. It is
true that 6.4.2.3p3 makes certain constructs undefined, but this does not
alter the tokenisation.

There are two ways to fix this situation. The first is to delete the second
alternative for preprocessing-token. The second would be to add text to
6.4p3, or as a footnote, along the following lines:

    The alternative "each universal-character-name" that cannot be one of
    the above can never occur in the initial tokenisation of a program in
    translation phase 3. However, if an identifier includes a universal-
    character name that is not listed in Annex I, the implementation may
    choose to retokenise using this alternative.


========
[Item 07]
Category: Normative change where the intent is unclear
Committee Draft subsection: 6.7.5.2

Title: Side effects in VLAs

Detailed description:

There has been a long discussion on both the reflector and comp.std.c
about the issue of side effects in VLA types. I do not intend to repeat
all the arguments for and against. However, after considering all the
issues it seems to me that the problems are to do with function prototypes
more than they are to do with VLAs or side effects. In particular, code
such as:

    int n;
    /* ... */
    int vla [n++];

is both meaningful and easy to compile; the problems come with constructs
like:

    void f (int n, int a [n++]);

Therefore the changes proposed below implement the following principles:

* If a declarator or an abstract-declarator is not part of a parameter-
  declaration, any expressions occuring in variably modified types are
  evaluated in the normal way, including all function calls and side
  effects.
* If the declarator or abstract-declarator *is* part of a parameter-
  declaration, any expressions within array declarators are *not* evaluated,
  and thus the behaviour is as if all such expressions were replaced with
  "*" (though the latter would be forbidden in a function definition).

An optional addition to the proposal enforces the latter rule with a
constraint that forbids side effects and function calls in such expressions.

Recommended changes:

In 6.7.5.2, add a new heading and paragraph before the constraints:

    Definitions

    The direct-declarator or direct-abstract-declarator which forms the
    array declarator can occur in one of three contexts, each of which
    is given a name:
    (1) It is derived from the parameter-type-list of the declarator of
    a function definition. This is a /parameter-array-declarator/.
    (2) It is derived from some other parameter-type-list. This is a
    /prototype-array-declarator/.
    (3) It is not derived from a parameter-type-list. This is an
    /actual-array-declarator/.

In the existing paragraph 3, replace the two sentences:

    If the size expression is not a constant expression, and it is
    evaluated at program execution time, it shall evaluate to a value
    greater than zero. It is unspecified whether side effects are
    produced when the size expression is evaluated.

with:

    If the size expression is part of a parameter-array-declarator or
    of an actual-array declarator, and is not a constant expression, it
    shall be evaluated at program execution time (in the former case on
    each entry to the function) and shall evaluate to a value greater
    than zero. Otherwise it shall not be evaluated.

Optional addition: add a further constraint:

    A parameter-array-declarator or prototype-array-declarator shall not
    contain a function call operator, nor shall it contain any operator
    which can modify an object.


========
[Item 08]
Category: Normative change to existing feature retaining the original intent
Committee Draft subsection: 6.8.5

Title: Error in new for syntax

Detailed description:

C9X adds a new form of syntax for for statements:

    for ( declaration ; expr-opt ; expr-opt ) statement

However, 6.7 states that /declaration/ *includes* the trailing semicolon.
The simplest solution is to remove the corresponding semicolon in 6.8.5 and
not worry about the informal use of the term in 6.8.5.3p1. Alternatively
the syntax needs to be completely reviewed to allow the term to exclude the
trailing semicolon.


========
[Item 09]
Category: Inconsistency
Committee Draft subsection: 6.9

Title: References to sizeof not allowing for VLAs

Detailed description:

6.9p3 and p5 use sizeof without allowing for VLAs. In each case, change
the parenthetical remark:

    (other than as a part of the operand of a sizeof operator)

to:

    (other than as a part of the operand of a sizeof operator which
    is not evaluated)


========
[Item 10, based on PC-UK0027]
Category: Normative change to existing feature retaining the original intent
Committee Draft subsection: 6.10.3

Title: Problems with extended characters in object-like macros

Detailed description:

When an object-like macro is #defined, there is no requirement for a
delimiter between the macro identifier and the replacement list. This can
be a problem when extended characters are involved - for example, some
implementations view $ as valid in a macro identifier while others do not.
Thus the line:

    #define THIS$AND$THAT(x) ((x)+42)

can be parsed in either of two ways:

    Identifier       Arguments   Replacement list
    THIS             -           $AND$THAT(x) ((x)+42)
    THIS$AND$THAT    x           ((x)+42)

TC1 addressed this by requiring the use of a space in certain circumstances
so as to eliminate the ambiguity. However, this requirement has been
removed in C9X for good reasons. Regrettably this reintroduces the original
ambiguity.

The simplest solution seems to be to require a space - or possibly one of
the basic graphic characters - between the identifier and the replacement
list. This change is unlikely to affect much code, and is not a Quiet
Change - it will require a diagnostic for any affected code. It has the big
advantage of eliminating the ambiguity.

Insert a new Constraint in 6.10.3, either:

    In the definition of an object-like macro there shall be white space
    between the identifier and the replacement list.

or

    In the definition of an object-like macro there shall be white space
    between the identifier and the replacement list unless the replacement
    list begins with one of the 26 graphic characters in the required
    character set other than ( _ or \.


========
[Item 11]
Category: Feature that should be included
Committee Draft subsection: 7.8

Title: Missing functions for intmax_t values

Detailed description:

Several utility functions have versions for types int and long int, and when
long long was added corresponding versions were added. Then when intmax_t
was added to C9X, further versions were provided for some of these
functions. However, three cases were missed. For intmax_t to be useful to
the same audience as other features of the Standard, these three functions
should be added. Obviously they should be added to <inttypes.h>.

Add a new subclause 7.8.3:

    7.8.3  Miscellaneous functions

    7.8.3.1  The atoimax function

    Synopsis

        #include <inttypes.h>
        intmax_t atoimax(const char *nptr);

    Description

    The atoimax function converts the initial portion of the string
    pointed to by nptr to intmax_t representation. Except for the
    behaviour on error, it is equivalent to

        strtoimax(nptr, (char **)NULL, 10)

    The function atoimax need not affect the value of the integer
    expression errno on an error. If the value of the result cannot
    be represented, the behavior is undefined.

    Returns

    The atoimax function returns the converted value.

    7.8.3.2  The imaxabs function

    Synopsis

        #include <inttypes.h>
        intmax_t abs(intmax_t j);

    Description

    The imaxabs function computes the absolute value of an integer j.
    If the result cannot be represented, the behavior is undefined.

    Returns

    The imaxabs function returns the absolute value.

    7.8.3.3  The imaxdiv function

    Synopsis

        #include <inttypes.h>
        imaxdiv_t div(intmax_t numer, intmax_t denom);

    Description

    The imaxdiv function computes numer/denom and numer%denom in a
    single operation.

    Returns

    The imaxdiv function returns a structure of type imaxdiv_t,
    comprising both the quotient and the remainder. The structure shall
    contain (in either order) the members quot (the quotient) and rem
    (the remainder), each of which have the type intmax_t. If either
    part of the result cannot be represented, the behavior is undefined.
 
7.8 paragraph 2 will need consequential changes.


========
[Item 12]
Category: Clarification
Committee Draft subsection: 7.19.5.1

Title: Clarify meaning of a failed fclose

Detailed description:

If a call to fclose() fails it is not clear whether:
- it is still possible to access the stream;
- whether fflush(NULL) will attempt to flush the stream.
It is probably best to take the view that fclose always "closes" the stream
as far as the program is concerned, whether or not the process was fully
successful.

The existing wording - read strictly - also requires the full list of
actions to be carried out successfully whether or not the call fails. This
is clearly an oversight.

Change 7.19.5.1p2 to read:

    A successful call to the fclose function causes the stream pointed
    to by stream to be flushed and the associated file to be closed.
    Any unwritten buffered data for the stream are delivered to the
    host environment to be written to the file; any unread buffered
    data are discarded. Whether or not the call succeeds, the stream
    is disassociated from the file, and if the associated buffer was
    automatically allocated, it is deallocated.


========
[Item 13]
Category: Correction restoring original intent
Committee Draft subsection: 7.23.3.7

Title: Wrong time system notation used

Detailed description:

In 7.23.3.7p2, the expression "UTC-UT1" appears. This should read "TAI-UTC".




-- 
Clive D.W. Feather   | Regulation Officer, LINX | Work: <clive@linx.org>
Tel: +44 1733 705000 | (on secondment from      | Home: <cdwf@i.am>
Fax: +44 1733 353929 |  Demon Internet)         | <http://i.am/davros>
Written on my laptop; please observe the Reply-To address