ISO/ IEC JTC1/SC22/WG14 N774

SC22/WG14 N774    J11/97-138


Assorted minor substantive issues
Clive D.W. Feather
clive@demon.net
1997-09-26


Abstract
========
This paper is assembled from those elements of N720, N735, and N739, and
are intended to improve the consistency and understandability of the
Standard. In each case there is, in theory, a normative change to the text,
but these changes should be uncontroversial and not affect any reasonable
program.

Items are given a serial number in this paper, but also carry a note statig
their origin. Items taken from N720 do not have a rationale; the related
DR explains the issues.

References are relative to Draft 11 pre 3.


Specific items
==============

Item 1
[Was N720 item DR 174]
----------------------
In subclause 6.2.1.7 paragraph 1, replace:
    Many binary operators that expect operands of arithmetic type cause
    conversions and yield result types in a similar way. The purpose is
    to yield a common type, which is also the type of the result.
with:
    Many operators cause the same pattern of conversions to be applied
    to two operands of arithmetic type. The purpose is to yield a common
    type, which, unless explicitly stated otherwise, is also the type of
    the operator's result.

In subclause 6.3.15, replace paragraphs 4 and 5 with:
    The first operand is evaluated; there is a sequence point after its
    evaluation. The second operand is evaluated only if the first compares
    unequal to 0; the third operand is evaluated only if the first
    compares equal to 0; the result of the operator is the value of the
    second or third operand (whichever is evaluated), converted to the
    type described below.

    If both the second and third operands have arithmetic type, the type
    that the usual arithmetic conversions would yield if applied to those
    two operands is the type of the result. If both the operands have
    structure or union type, the result has that type. If both operands
    have void type, the result has void type.

and change the last sentence of paragraph 6 to end:
    ... in which case the type of the result is pointer to void.


Item 2
[Was N720 item DR 163]
----------------------
Add a new Constraint section to 6.3.1:

    Constraint

    Except for function calls as described in 6.3.2.3, there shall be a
    declaration of any identifier visible at the point it is used as a
    primary-expression.


Item 3
[Was N720 item DR 070]
----------------------
In subclause 6.3.2.3 paragraph 5, after:

    ... the types of the arguments after promotion are not compatible
    with those of the parameters after promotion, the behaviour is
    undefined.

insert:

    with one exception: if the two types are required to have the same
    representation for some or all values, the behaviour is undefined
    only if the value of the argument is not such a value. [*]

    [*] Thus a nonnegative signed integer may be passed to a parameter
    that has the corresponding unsigned type, and a pointer to void
    may be passed to a parameter with pointer to a character type.

In subclause 6.3.2.4 paragraph 5, change "one exception" to
"two exceptions", and append:

    Secondly, if the two members have types which are required to have
    the same representation for some or all values, the value stored may
    be accessed using any member with a type for which that value must
    have the same representation.


Item 4
[Was N720 item DR 115]
----------------------
Change subclause 6.5 paragraph 2 to:

    A declaration shall declare at least a declarator (excluding the
    parameters of a function or the members of a structure or union), a
    tag, or the members of an enumeration.


Item 5
[Was N720 item DR 165]
----------------------
Replace subclause 6.5.2.3 by the following wording (taken from the DR):

    6.5.2.3 Tags

    Constraints

    A specific type shall have its content defined at most once.

    A type specifier of the form
         enum identifier
    without an enumerator list shall only appear when the type it
    specifies is complete.

    Semantics

    All declarations of structure, union, or enumerated types that
    have the same scope and use the same tag declare the same type. The
    type is incomplete [F1] until the closing brace of the list defining
    the content, and complete thereafter.

    [F1] An incomplete type may only be used when the size of an object
    of that type is not needed. [Append the present footnote 99, or see
    below for alternative wording.]

    Two declarations of structure, union, or enumerated types which
    are in different scopes or use different tags declare distinct types.
    Each declaration of a structure, union, or enumerated type which does
    not include a tag declares a distinct type.

    A type specifier of the form
        struct-or-union identifier/opt { struct-declaration-list }
    or
        enum identifier/opt { enumerator-list }
    declares a structure, union, or enumerated type. The list
    defines the /structure content/, /union content/, or /enumeration
    content/. If an identifier is provided [F2], the type specifier also
    declares the identifier to be the tag of that type.

    [F2] If there is no identifier, the type can, within the translation
    unit, only be referred to by the declaration of which it is a part.
    Of course, when the declaration is of a typedef name, subsequent
    declarations can make use of that typedef name to declare objects
    having the specified structure, union, or enumerated type.

    A declaration of the form
        struct-or-union identifier ;
    specifies a structure or union type and declares the identifier as
    the tag of that type [F3].

    [F3] A similar construction with /enum/ does not exist.

    If a type specifier of the form
        struct-or-union identifier
    occurs other than as part of one of the above constructions,
    and no other declaration of the identifier as a tag is visible,
    then it declares a structure or union type which is incomplete at
    this point, and declares the identifer as the tag of that type [F3].

    If a type specifier of the form
        struct-or-union identifier
    or
        enum identifier
    occurs other than as part of one of the above constructions, and a
    declaration of the identifier as a tag is visible, then it specifies
    the same type as that other declaration, and does not redeclare the tag.

[Retain the existing examples.]

Note: at the 1993 London meeting I took an action item to rewrite this
footnote to explain the issues properly, pursuant to a DR response. The
final text was sent to the convenor of the time (P.J.Plauger). Applying
that text, the footnote would become:

    [F1] An incomplete type may only be used when the size of an object
    of that type is not needed. The size of an object of a specific type
    is needed, and therefore the type shall be complete, in the following
    circumstances:
    - when the size of an object of any of the following types is needed:
      * a qualified version of that type
      * a structure or union type with a member of that type
      * an array type with that type as its element type
    - when that type, or an expression of that type, appears as the
      operand of the sizeof operator;
    - when a variable of that type is defined (this excludes tentative
      definitions but includes the implicit definition described in 6.7.2);
    - when an expression of type pointer to that type occurs as either
      operand of an additive or relational operator;
    - when a function returning that type is defined.

I also recommended that the footnote belonged in 6.1.2.5 rather than here.


Item 6
[Was N720 item DRs 096 and 110]
-------------------------------
In subclause 6.5.5.2 add a further constraint:

    The element type shall not be an incomplete or function type.


Item 7
[Was N720 item DR 084]
----------------------
In subclause 6.5.5.3 add a further constraint:

    The parameters in a parameter-type-list that is part of a function
    definition shall not have incomplete type.

and a new Semantics paragraph after paragraph 6:

    If the function declarator is not part of a function definition, the
    parameters may have incomplete type.


Item 8
[Was N720 item DR 173]
----------------------
In subclause 6.8.4 paragraph 2, change:
    ... to the current token.
to:
    ... up to an unspecified character within the current token.


Item 9
[Was N720 item DR 176]
----------------------
Replace subclause 6.8.5 with the following wording, based on that in the
DR but modified to take account of changes since then:

    6.8.5 Error directive

    Constraints

    A #error preprocessing directive shall not occur in a preprocessing
    translation unit except as part of a group skipped as part of
    conditional inclusion. Any diagnostic message generated because of
    the violation of this constraint [*] shall include the sequence of
    preprocessing tokens in the directive.

    [*] The intent of this subclause is that #error that is not skipped
    indicates that translation should fail.


Item 10
[Was N735 item 5]
-----------------
Change the first sentence of subclause 7.1.2 paragraph 1 from:

    Each library function is declared in a /header/, [135] ...

to:

    Each library function is declared, with a type that includes a
    prototype, in a /header/, [135] ...

The as-if rule means that this need not be done literally, provided that
the effects of argument assignment rather than default promotion (other
than trailing varargs, of course) will happen to all library function
calls.


Item 11
[Was N735 item 6]
-----------------
A careful reading of subclause 7.3.1 shows that, for characters outside
the 95-element minimal execution character set, there are two sets of
classification macros that are significant. For each set, a character
can belong to at most one member of the set. The following table shows
these sets, examples of characters within those sets taken from the
minimal 95, and cases that cannot happen:

                     isprint()      iscntrl()      [neither]

    isalpha()           'A'         forbidden         =1=

    isspace()           ' '            '\n'           =2=

    [neither]           ':'            '\b'           '\0'

The interesting cases are those marked =1= and =2=; any characters with
these properties must be locale-specific.

The question turns on the intended meaning of "printable". The current
definition requires the character to occupy a position on a printing
device.

Option A
--------
If so, such characters do make sense - =1= could be a "dead" character
that overprints another one, or =2= could be a hair-thin space. Then
attach a footnote to subclauses 7.3.1.2 (isalpha()), 7.3.1.6 (islower()),
and 7.3.1.10 (isupper()):

    [*] The additional characters might not be printing characters; for
    example, they may be "dead" characters that overprint the preceeding
    or following character and are thus not "printing".

Option B
--------
However, it is questionable whether the term "one printing position"
still has a meaning in this day of proportional-spaced output devices,
and whether there is a need for a better definition of "printable". In
this case, change the definition to:

    The term /printing character/ refers to a member of an
    implementation-defined set of characters, each of which has a
    characteristic appearance on a display device and usually occupies
    one printing position;

in subclauses 7.3.1.2 (isalpha()), 7.3.1.6 (islower()), and 7.3.1.10
(isupper()), change:

    ... locale-specific set of characters ...

to:

    ... locale-specific set of printing characters ...

and in 7.3.1.9 (isspace()) change it to:

    ... locale-specific set of printing or control characters ...


Item 12
[Was N735 item 12]
------------------
Change the following C locale values in 7.5 (<locale.h>) paragraph 2 from:

    mon_decimal_point  ""
    negative_sign      ""

to:

    mon_decimal_point  "."
    negative_sign      "-"


Item 13
[Was N720 item DR 066]
----------------------
In subclause 7.5.2.1 paragraph 3, insert after "of zero length":
    Apart from /grouping/ and /mon_grouping/, the strings shall start
    and end in the initial shift state.


Item 14
[Was N735 item 8]
-----------------
In subclause 7.10.1.1 (setjmp()) there is a heading "Environmental
constraint". This implies that the sentence is a Constraint, and that
violation requires a diagnostic. It is reported that very few
implementations generate such a diagnostic, and that most implementations
correctly handle other contexts.

Change the heading to "Environmental restriction" and add at the end:

    If the invocation appears in any other context, the behaviour is
    undefined.


Item 15
[Was N720 item DR 140]
----------------------
In subclause 7.13.5.6 (setvbuf()) paragraph 2, change:
    ... any other operation ...
to:
    ... any other operation (other than an unsuccessful call to
    /setvbuf/) ...


Item 16
[Was N735 item 17]
------------------
Option A
--------
Add to subclause 7.14.4.2 (atexit()) paragraph 2:
    Whether the function is called on abnormal program termination is
    unspecified.

Option B
--------
Add to subclause 7.14.4.2 (atexit()) paragraph 2:
    Whether the function is called on abnormal program termination is
    implementation-defined.


Item 17
[Was N720 item DR 134]
----------------------
In subclause 7.15.6.2 (strerror()), append to paragraph 2:
    If the argument is not zero, EDOM, ERANGE, or any value that a
    library function might store in errno, the behaviour is undefined.


Item 18
[Was N735 item 18b]
-------------------
The Standard provides no way to determine whether realloc() has moved
the memory; this is something you want to do if you have pointers to
within the block of memory. If it hasn't moved, the returned pointer
will compare equal to the pointer argument. But if it has, you cannot
make the comparison because a pointer to freed memory (and thus to
moved memory) is indeterminate, and the comparison is undefined
behaviour (unless you go through hoops like using memcpy()).

There is a rationale behind this last part (making a legitimate value
suddenly become illegitimate): some implementations may check pointers
for validity whenever they are loaded into a register. However, it is
a problem.

Should the comparison be permitted ? Is it desirable to provide at
least some mechanism to determine if the memory has moved ?


Item 19
[Was N739 item 9b]
------------------
[This should be deferred until the undefined v implementation-defined
issue has been resolved.]

If a union is read from a member other than the one last stored into,
the result is currently implementation-defined. Because the result might
cause a trap of some kind (e.g. invalid pointer), it should be undefined
behaviour in most circumstances; the wording should broadly follow 6.3
on this matter.

In 6.3.2.4, replace paragraph 5 (either the original or that derived from
the changes in item 3) with:

 |  With two exceptions, if the value of a member of a union object is
 |  used when the most recent store to the object was to a member whose
 |  type does not have the same alignment and representation, the
 |  behaviour is undefined. If either member has character type or is an
 |  array of character type, the behaviour is implementation-defined [68].
 |  Furthermore, a special guarantee is made ...

==== ENDS ====