main() in C90

J

jameskuyper

jacob said:
(e-mail address removed) wrote: ....

Yes.

What is the philosophy behind

"C is so full of warts that fixing this one doesn't matter" ???

I don't know. I've seen people express that attitude, but almost by
definition they are no longer users of C, or at least they do not
remain users of C for very long after having felt that way.

My own attitude is quite different from that. I feel that backward
compatibility is very important, and that to a lesser extent, so is
having a consistent design philosophy. I believe that changes to the
language which are seriously inconsistent with backwards compatibility
or with C's basic design philosophy should result in the creation of a
new language, not a modified version of C. As long as C's design
philosophy is adhered to, those other languages won't replace C,
they'll just take up different niches in the computing world.

The kind of automatic checking Walter Roberson suggested is more
appropriate for a higher-level language. One key aspect of the C
design philosophy is that you shouldn't have to pay for features you
don't need. It's straightforward, though admittedly error prone, to
write C code in such a way that a range check is performed once, and
then never repeated, because the developer has correctly relied upon
the assumption that the length of the array it applies to won't change
(if it will change, for instance because it's a VLA or because it's
dynamically reallocated, then that's not the case I'm talking about)..
If you've gone to the trouble of writing your code that way, you
should be able to get the small but real performance gain you've
already paid for, by not having redundant bounds-checks automatically
inserted into your code by the compiler. Mandating such bounds checks
would kill C, at least for some platforms. I suspect that would be
especially true in the embedded world, where a large portion of the
current work using C is being done.

....
There is NO reason to specify in that way asctime(). It can be done
correctly without any problems!

o snprintf would solve that

That's what I've already advocated.
 
R

Randy Howard

Yes, it tells us that MS doesn't understand the C language.

That, or it tells us that they realize they allows to provide
extensions, and they realize that on their platform's runtime, not
returning a value is acceptable.
 
H

Harald van Dijk

Harald van D?k said:
[asctime]
jacob navia said:

The behavior for out of range arguments is NOT SPECIFIED. They didn't
take the trouble of SAYING that years should be smaller than 10000,
and months/days should be smaller than 31.

The limits are easily deduced from 4.12.3.1 of C90 or 7.23.3.1 of C99.
For the buffer not to overflow:
[...]
With asctime(), the problem is non-obvious and merely quietly
suggested.

Sorry, but I must disagree here. We're talking about the C Standard
document itself. If you don't - er, sorry, let me re-word that: if one
does not read the document, one will not read any explicit warnings it
contains. And if one does read the document, or at least the asctime
part of it (which is far from long-winded), one will see a 26-byte
buffer being handed to sprintf in the code (asctime provides C code as
an "as if" for the algorithm to be used). That hard-coded buffer should
be a bit of a clue.

Well, yes, but in any other context, it would be a clue that the function
definition may be defective, rather than an intentional restriction on
the input. In any other context, I believe there would be no disagreement
that if 1900 + tm_year > 9999, then the intent would have been to print a
five-plus-digit number, and the maximum required buffer size was merely
miscalculated.
Furthermore, the wording "The asctime function
converts the [...] time [...] into a string" should *immediately* ring
bells in any C programmer's head: "What string, where? How big? Who owns
it? Do I have to free it? Will I overwrite the previous value if I
printf("%s-%s\n", asctime(x), asctime(y))?"

That I can agree with, those are questions that should be answered by
inspecting the function definition.
So even if one didn't happen
to notice the C-as-algorithm before, sure as eggs is eggs one will
notice it when one looks for the answers to those questions. So one
*will* spot the limited size of the array, and from that and the sprintf
call it is trivial to deduce the ranges that I did deduce

If you look at the code as the actual implementation, sure, but I looked
at it as a description before anything else.
(and not quite
so trivial to spot that you can actually extend the year range back a
further not-quite-three millennia).

It was the other way around for me. The way I looked at the function
definition, I first saw that any integer without restriction was printed,
and only after that (actually, only after reading about the problem) that
the buffer might not be large enough.

Regardless of which of those is clearer, though, I'm sure you can agree
the problem with gets tops both, yet that has had a mention in the
rationale long before it became deprecated, while no such mention is
given to asctime.
 
J

jacob navia

Ioannis said:
Couldn't understand what you mean. Rephrase it.

He means that I am such a bad beast that the C++ people
will be angry with you because you brought them the
terrible horror of my presence.

CBFalconer likes to insult me like this, oblique, and
utterly stupid. He must feel so good after he writes that
nonsense.

Well, each one his hobbies I suppose.
 
W

Walter Roberson

WHAT IS THIS?
Since UB corrupts memory anyway, let's put UB in the C standard

Oh, well, if that's what it would take to make you happy, then
I'd be all for adding a sentance or clause to 3.18 NOTE
specifically indicating that memory corruption is one of the
possibly undefined behaviours. Currently it says,

NOTE Possible undefined behavior ranges from ignoring
the situation completely with unpredictable results, to
behaving during translation or program execution in a
documented manner characteristic of the environment (with or
without the issuance of a diagnostic message), to
terminating a translation or execution (with the issuance of
a diagnostic message).


Personally, I had always understood "unpredictable results"
to include memory corruption, but if that isn't sufficiently
clear, then by all means mention memory corruption specifically.

so that anyone that reads it can laugh:
"Look at this C heads. They put a buffer overflow in their
standards document"


In any language that does not mandate run-time bounds checking and
run-time pointer checking, memory corruption can *and will* occur.
That fact might be a source of amusement to some people, but
it is a fact of life for "bare to the metal" programming languages.
 
C

CBFalconer

jacob said:
.... snip ...

There is NO reason to specify in that way asctime(). It can be
done correctly without any problems!

o snprintf would solve that
o a few tests for wrong values would solve that.

Why leave it like that?

Below is the appropriate section of the standard, copied from
N869.txt. Note that the output string is specified as holding 26
chars, the last of which will be a '0'. That string is static to
the function (or equivalent). The only possibility of an overrun
error is a bad value for timeptr->tm_year, and values from -1900
through 8099 are already handled. You _could_ add an unsigned int
and transfer the modified year to it, and then send an evil
message. i.e.:

unsigned int testyr;
...
if (10000 <= (testyr = 1900U + timeptr->time_yr)) {
strcpy(result, "You boob, time_yr is evil");
/* 1234567890123456789012345 */
return result;
}
... continue with existing code ...

but that is up to the people building the library. N869 quote:

7.23.3.1 The asctime function

Synopsis
[#1]
#include <time.h>
char *asctime(const struct tm *timeptr);

Description

[#2] The asctime function converts the broken-down time in
the structure pointed to by timeptr into a string in the
form
Sun Sep 16 01:03:52 1973\n\0

using the equivalent of the following algorithm.

char *asctime(const struct tm *timeptr) {
static const char wday_name[7][3] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static const char mon_name[12][3] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static char result[26];

sprintf(result, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
wday_name[timeptr->tm_wday],
mon_name[timeptr->tm_mon],
timeptr->tm_mday, timeptr->tm_hour,
timeptr->tm_min, timeptr->tm_sec,
1900 + timeptr->tm_year);
return result;
}

Returns

[#3] The asctime function returns a pointer to the string.
 
C

CBFalconer

jacob said:
.... snip ...

I want asctime(à fixed because it can be done VERY EASILY!!!
It suffices to specify correctly the size of the buffer OR
the allowed range of the arguments. That is not rocket science!

No, it can't have the buffer size adjusted. It is specified in the
C standard.
 
K

Keith Thompson

jacob navia said:
Walter Roberson wrote: [...]
Jacob specifically quoted the DR about "Corrupting memory" being
part of "the range of undefined behavior", and took exception to that.
That is a more general dissatisfaction than with the result of
the single library function asctime(). Fixing asctime() alone would
not fix the more general issue that as far as C is concerned,
corrupting memory is an acceptable part of the range of
"undefined behavior".

NO!

I want asctime(à fixed because it can be done VERY EASILY!!!
It suffices to specify correctly the size of the buffer OR
the allowed range of the arguments. That is not rocket science!

Of course I am not against eliminating all possible UB in all
possible programs, what are you talking about?
Your proposal is therefor just a band-aid over one particular wart;
removing the possibility that corrupting memory is permitted
behavior for "undefined behaviour" everywhere in the C standard would
require the sort of checks I outline, such as mandatory bounds checking
and run-time memory alignment checking for unions.

Look Mr, maybe removing THIS wart is not such a big deal but it would
be at least THIS WART GONE!

What is this philosophy?

"C is so fucked up that removing this wart wouldn't make any difference"

jacob, if you stopped SHOUTING for just a moment you might realize (a)
that a lot of us here actually agree with you that the asctime
specification in the standard can and should be fixed, and (b) that
most of us here are in no position to do anything about it.

Having said that, I think you're underestimating the difficulty of
making such a change, at least outside the context of a major revision
to the standard. For example, I think it would have been fairly
straightforward to fix it in C99, but harder, for political and other
reasons, to fix it in a DR or a TR.

Since the behavior for out-of-range arguments is undefined,
implementations are already free to do something sensible. Some have
suggested using snprintf(), so the result will be truncated (and if
so, the lack of a trailing new-line could cause some problems on
output, but that's better than UB). Others have suggested replacing
some fields with '*' characters. Both are sensible ideas, and both
have probably already been implemented. Another possibility is just
to make the static buffer big enough for any possible arguments (this
can be determined given the range of int). Mandating any of these
solutions would break existing implementations that already use a
different one. In my opinion it would be worth the cost, but others
might disagree.

Or the standard could just mandate that it will never overflow the
buffer, which I suppose would be an improvement over the current
situation, but it would still be impossible for programs to depend on
the result for out-of-range arguments.

Fixing asctime() is easy. Reaching agreement on a solution that must
be imposed on all implementations is less easy.

The last sentence of DR #217
<http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_217.htm> is

There is no consensus to make the suggested change or any change
along this line.

It's conceivable that there was agreement that it should be changed,
but no consensus on how to do it. (I don't claim that that's a likely
interpretation.)

And even if the committee amended DR #217 tomorrow morning, and
incorporated it into TC4 and published it next week, how long would it
be before *all* implementations supported the new semantics? How long
would it be before C programmers could confidently assume that the
asctime() function for *all* implementations on which their code might
be executed conforms to the new requirements? It wouldn't be forever,
but it would be long enough that programmers would still be well
advised to check the arguments themselves first.

Nobody is forcing anybody to use asctime() in the first place.
Personally, I wouldn't even want to use it in new code. Its output
format is ugly and USA-specific, and the trailing new-line causes
problems. I'd much rather use strftime(), which is vastly more
flexible and avoids all of asctime()'s problems (at the expense of
being a bit more complex to use).

I'd be willing to bet that the vast majority of arguments to asctime()
use values that were derived from the result of the time() function,
either immediately before the call or some time in the past.

Have you ever seen an actual bug in an actual program caused by this
issue?

Yes, the UB in asctime() is a wart, and yes, I'd like to see it fixed.
But it's neither trivial, nor vitally important, nor worth a flame war
among people who can't do anything about it.

Just out of curiosity, how does lcc-win's asctime() behave?
 
H

Harald van Dijk

No, it can't have the buffer size adjusted. It is specified in the C
standard.

Well obviously it's possible to change the standard. It's been done
before. So I'm assuming you're referring to implementations supporting
the current standard.

In what way would an implementation of asctime with a larger buffer size
than specified in the C standard fail to conform? For all input to
asctime, either the behaviour is defined, sprintf will write exactly 26
bytes, and there's no way for the calling program to see that extra bytes
have been reserved, or the behaviour would be undefined, and the effects
of using a larger buffer allows the input as an extension.
 
C

CBFalconer

CBFalconer said:
jacob navia wrote:

... snip ...


Below is the appropriate section of the standard, copied from
N869.txt. Note that the output string is specified as holding 26
chars, the last of which will be a '0'. That string is static to
the function (or equivalent). The only possibility of an overrun
error is a bad value for timeptr->tm_year, and values from -1900
through 8099 are already handled. You _could_ add an unsigned int
and transfer the modified year to it, and then send an evil
message. i.e.:

unsigned int testyr;
...
if (10000 <= (testyr = 1900U + timeptr->time_yr)) {
strcpy(result, "You boob, time_yr is evil");
FIX--> strcpy(result, "Booblet, time_yr is evil\n");
/* 1234567890123456789012345 */
return result;
}
... continue with existing code ...

Make the above indicated fix! :)
 
J

James Kuyper

CBFalconer said:
jacob navia wrote:
... snip ...

No, it can't have the buffer size adjusted. It is specified in the
C standard.

No, that specification is only intended to convey the required behavior,
when the behavior is defined; it is not a required way of implementing
it. There is no way for strictly conforming code to detect the fact that
the buffer is larger than 26, because any code that would fill that
buffer with a string longer than that has undefined behavior.
 
C

CBFalconer

James said:
No, that specification is only intended to convey the required
behavior, when the behavior is defined; it is not a required way
of implementing it. There is no way for strictly conforming code
to detect the fact that the buffer is larger than 26, because
any code that would fill that buffer with a string longer than
that has undefined behavior.

The specification says:

"using the equivalent of the following algorithm."

and the following algorithm specifies a static buffer size of 26.
This leaves no option, IMO. It does allow implementation in
assembly code, or in Druidism, as pleases the implementor.
 
K

Keith Thompson

CBFalconer said:
Below is the appropriate section of the standard, copied from
N869.txt. Note that the output string is specified as holding 26
chars, the last of which will be a '0'.

You mean '\0', not '0'.
That string is static to
the function (or equivalent). The only possibility of an overrun
error is a bad value for timeptr->tm_year, and values from -1900
through 8099 are already handled.
[...]

Values of timeptr->tm_year anywhere in the range -2899 through 8099
(representing years -999 to 9999) are safe.

Not quite. The format string, again, is

"%.3s %.3s%3d %.2d:%.2d:%.2d %d\n"

Any of the "%...d" specifiers could result in an arbitrarily long
string (up to the width of INT_MIN) given a sufficiently large
argument. The "%3d" and "%.2d" specifiers, unlike "%.3s", don't
truncate their arguments.

For example, this program:

#include <stdio.h>
#include <limits.h>
int main(void)
{
printf("%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
"Sun", "Sep", INT_MIN, INT_MIN, INT_MIN, INT_MIN, INT_MIN);
return 0;
}

produces this output (on a system with INT_MIN==-2147483648):

Sun Sep-2147483648 -2147483648:-2147483648:-2147483648 -2147483648

Using sprintf this would require 68 bytes.

And of course the sample implementation given in the standard uses
unchecked arguments as array indices. A value of timeptr->tm_wday
outside the range 0..7, or of timeptr->tm_mon outside the range 0..11,
would invoke undefined behavior even before the result string is
built.

And just to add to the frivolity, values of tm_sec occupying 3
characters in the result (rather than the usual 2) are safe if tm_year
only takes up 3 characters (rather than the usual 4), along with a
plethora of other permutations.

(The workaround is easy enough: be careful when using asctime(), or
use strftim() instead.)
 
R

Richard Heathfield

CBFalconer said:
The specification says:

"using the equivalent of the following algorithm."

and the following algorithm specifies a static buffer size of 26.
This leaves no option, IMO. It does allow implementation in
assembly code, or in Druidism, as pleases the implementor.

The "as if" rule applies. Since a strictly conforming program can't tell
whether a larger buffer is allocated, it's okay to allocate a larger
buffer.
 
C

CBFalconer

Keith said:
CBFalconer said:
Below is the appropriate section of the standard, copied from
N869.txt. Note that the output string is specified as holding 26
chars, the last of which will be a '0'.

You mean '\0', not '0'.
That string is static to
the function (or equivalent). The only possibility of an overrun
error is a bad value for timeptr->tm_year, and values from -1900
through 8099 are already handled.
[...]

Values of timeptr->tm_year anywhere in the range -2899 through 8099
(representing years -999 to 9999) are safe.

Not quite. The format string, again, is

"%.3s %.3s%3d %.2d:%.2d:%.2d %d\n"

Any of the "%...d" specifiers could result in an arbitrarily long
string (up to the width of INT_MIN) given a sufficiently large
argument. The "%3d" and "%.2d" specifiers, unlike "%.3s", don't
truncate their arguments.

No. The input is specified as a struct tm*, and that struct is
specified as having restricted value sizes. I believe they never
exceed 60, and are never negative. Some are 3 char. strings. That
struct is, in turn, created by provided code that interfaces with
some system specific timer.
 
H

Hans Schneider

Billy said:
Hi, in C90 is "int main()" valid, the same as "int main(void)" and "int
main(int argc, char *argv[])"?


AFAIK in C99 only "int main(void)" and "int main(int argc, char *argv[])
- and the **argv syntax" are the only valid ones.

In reality, "void main()" is also valid.

That's what I thought always, but I'm not anymore so sure. According to
a recent thread...

* H. Schildt (book writer and ISOC member)
- writes that you are free to use void main()
* Richard Heathfield (book writer)
- says that void main() is false
* Chris Hills (ISOC member)
- says that void main() is valid C
* Jack Klein (writer of C library and inventor of Klein Bottle)
- says that void main() is not valid C code

I am still very confused.

Some compilers issue a warning for this, but you can safely ignore this
warning in 99.999999% or six sigma of the time.

You're more likely to be struck by lightning than come across a compiler
that does not both accept and work with no problems with "void main()".

Microsoft supports "void main()", so that should tell you something,
provided you're living in the real world, of course.

What is ERRORLEVEL on Microsoft OS when main returns void? Is it 0?
 
R

Richard Heathfield

Hans Schneider said:
Billy said:
Hi, in C90 is "int main()" valid, the same as "int main(void)" and "int
main(int argc, char *argv[])"?


AFAIK in C99 only "int main(void)" and "int main(int argc, char
*argv[]) - and the **argv syntax" are the only valid ones.

In reality, "void main()" is also valid.

That's what I thought always, but I'm not anymore so sure. According to
a recent thread...

* H. Schildt (book writer and ISOC member)
- writes that you are free to use void main()
* Richard Heathfield (book writer)
- says that void main() is false
* Chris Hills (ISOC member)
- says that void main() is valid C
* Jack Klein (writer of C library and inventor of Klein Bottle)
- says that void main() is not valid C code

I am still very confused.

The return type of main isn't a matter of opinion. It can be resolved with
reference to the ISO C Standard.
What is ERRORLEVEL on Microsoft OS when main returns void? Is it 0?

Bearing in mind that the behaviour is in any case undefined, the answer is
that it is up to the implementation. I ran a couple of tests, and found
that one Microsoft implementation did indeed set ERRORLEVEL to 0, whereas
one Borland implementation set it to 10 (for one given test - YMMV).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,780
Messages
2,569,608
Members
45,246
Latest member
softprodigy

Latest Threads

Top