From: scs@adam.mit.edu (Steve Summit)
Subject: Re: Strings and integer sizes
Organization: none, at the moment
Date: Fri, 9 Oct 1992 17:37:55 GMT

In article <395@ulogic.UUCP>, hartman@ulogic.UUCP (Richard M. Hartman) writes:
> In article <3568@carrousel.and.nl> jos@and.nl (Jos Horsmeier) writes:
>> [quotes ANSI C Standard and, with copious explanations, concludes that]
>> I disagree that char zoe[3] = "123"; would be an erroneous initializer.
> 
> It is erroneous.

False, as Lars has already pointed out.  Jos was right to begin
with:

	zoe[3] = "123";

is perfectly legal, although whether it's a good idea or not is
an entirely different question.

But along comes Mike Stefanik (mike@pacsoft.com), saying exactly
the same thing in article <1417@pacsoft.com>:

> This *is* an erroneous initializer because you are initializing a three-
> character array with four characters. The side effects can be subtle; for
> example, consider the following program:
> 
>     char foo[3] = "foo";
>     char bar[3] = "bar";
>     
>     main()
>     {
>         printf("%s is %d characters\n", foo, strlen(foo));
>     }
> 
> On my system (UNIX, GCC compiler) this produces "foobar is 6 characters".
> On your box, it may cause a core dump, or (worst case) appear to act correctly
> even when memory is being inappropriately stomped upon.

Perhaps it depends on your definition of "erroneous."  Any error
in the above code fragment was made by the programmer, not by the
compiler.

It Just So Happens that the Unix compilers and linkers tend to
place objects contiguously in memory if they were contiguous in
the source code.  So the array-of-three-characters foo[] ended up
right next to the array-of-three-characters bar[].  But it is
wrong to pass either foo or bar to strlen or printf %s, because
both accept a string, which is a pointer to a sequence of
\0-terminated characters, and, by apparent design, neither foo
nor bar is \0-terminated.

(It's been mentioned several times during this thread, but
"string" is a weakly-defined concept in C, not a formal part of
the language except by convention, and by the fact that string
literals generate them and many standard library routines expect
them.)

No memory was overwritten in the above fragment.  The first
character in bar[] did not step on foo[]'s \0-termination,
because foo[] was explicitly requested to be 3 chars, which
didn't leave room for \0-termination.  strlen returned 6, and not
some infinite number, only because there Just Happened to be a 0
in the byte following bar[].  The only possibility for core dump
was that strlen and/or printf %s could have run off the end of
memory looking for a \0 that it might never find.  (Even if the
compiler had been somehow screwing things up by "overwriting
memory," which even buggy compilers and linkers rarely do, and
which in this case they would have no cause to do, such
overwriting would have happened at compile or link or program
load time, none of which typically generate core dumps.  But,
again, the compiler didn't screw things up.)

I'll grant that this is a funny situation, easy to get confused
about.  To review, let's look at a few more examples:

	char a1[] = "abc";
	char a2[] = {'a', 'b', 'c', '\0'};

	char a3[4] = "abc";
	char a4[4] = {'a', 'b', 'c', '\0'};

These all define 4-character arrays, containing the string "abc".

	char a5[3] = "abc";
	char a6[3] = {'a', 'b', 'c'};
	char a7[] = {'a', 'b', 'c'};

These declare 3-character arrays, containing the characters 'a',
'b', and 'c'.  Since these characters, though contiguous, are not
terminated by a '\0' character, they are not really "strings."

	char a8[2] = "abc";
	char a9[2] = {'a', 'b', 'c', '\0'};
	char a10[2] = {'a', 'b', 'c'};

These are all erroneous, and will be flagged as such by a correct
compiler, because there are too many initializers for the
explicit size requested.

	char a11[5] = "abc";
	char a12[5] = {'a', 'b', 'c', '\0'};
	char a13[5] = {'a', 'b', 'c', '\0', '\0'};

These define 5-character arrays, with their first four characters
containing the string "abc".  For a12 and a13, the fifth
character will be a '\0' if the arrays are global, external, or
static (if they have "static duration," in the ANSI C
terminology), or it will be unpredictable garbage if the arrays
are local non-static ("automatic duration").  For a13, the fifth
character will always be '\0'.  (Beware, though, that pre-ANSI
compilers generally didn't allow initializers for automatic
aggregates at all.)

a5 is the weird case.  It is permitted by special language in the
ANSI C Standard (section 3.5.7 p. 73 lines 14-15; see also the
Example p. 75 lines 24-29); it is admitted (Rationale, pp. 57-58)
to be an irregularity.  If you think it's too weird for words, or
not useful, and/or dangerous, you're not alone.  But it's a part
of the current standard (evidently there was historical precedent
for the behavior), so to state that a definition like that for
zoe[] or a5[] is "erroneous" is erroneous.

(By the way, I can't prove that the definition of a8[] must
elicit a diagnostic from a conforming compiler.  String literal
initializers are not mentioned in section 3.5.7 Constraints, only
in Semantics, where excess initializers are not mentioned.  It's
not clear that a single character in a string literal identifier
is formally an "initializer in an initializer list."  Someone
could bring up this point in comp.std.c, if they wanted to.)

					Steve Summit
					scs@adam.mit.edu