I am learning C: a little problem with a simple source code

J

James Kuyper

On 11/04/2011 05:21 PM, ImpalerCore wrote:
....
I'm curious about the whole 'int' return type placed on 'main' as
well. The only idea that I can come up with is that since Ritchie
designed C to support the Unix operating system, and the Unix
operating system wanted to implement pipes for inter-program
communication, 'int main(...)' became the de-facto standard to use the
return value in that sense.

AFAIK, the exit status from a program is used in Unix-like systems only
for communication between child processes and their parents, pipes need
not be involved, and usually aren't. In my personal experience, a shell
(sh, csh, tcsh, bsh, etc.) and make are the most common parent
processes. I'm not aware of any special connection between the exit
status and the use of pipes. Therefore, I find it odd that you might
consider pipes to have been the prime motivation for exit statuses.
Could you explain that idea in a little more detail?

Keep in mind that in C as it was originally designed, there was no
'void'. Functions declared with no return type were implicitly treated
as if they had a return type of 'int'. There were functions which didn't
bother setting the return value, and function calls which didn't bother
checking the return value, but there was no such thing as functions that
were declared as not returning a value. In such an environment, it would
be natural for main() to return an int, even if there was no particular
need to; and there was certainly a need for some mechanism whereby a
child process could report simple status information to its parent.
... What's interesting is that there are
other scenarios where returning a value from 'main' makes little to no
sense, like in embedded systems designed to run a single stand-alone
application "forever", with no opportunity for inter-process
communication, and yet 'void main(...)' is ridiculed even though it
seems to have a logical place in some application design domains.

I wouldn't ridicule void main(); the only thing I've ever done is point
out that it's not supported by the standard. If it were added to the
list of permitted signatures for main(), I'd have no objection.
 
I

ImpalerCore

On 11/04/2011 05:21 PM, ImpalerCore wrote:
...


AFAIK, the exit status from a program is used in Unix-like systems only
for communication between child processes and their parents, pipes need
not be involved, and usually aren't. In my personal experience, a shell
(sh, csh, tcsh, bsh, etc.) and make are the most common parent
processes. I'm not aware of any special connection between the exit
status and the use of pipes. Therefore, I find it odd that you might
consider pipes to have been the prime motivation for exit statuses.
Could you explain that idea in a little more detail?

Yes, you're correct. I just used "pipes" in the sense of "one program
communicating with another". It was phrased poorly. Is there a
better term?
Keep in mind that in C as it was originally designed, there was no
'void'. Functions declared with no return type were implicitly treated
as if they had a return type of 'int'.

Yeah, I seem to keep forgetting that.
There were functions which didn't
bother setting the return value, and function calls which didn't bother
checking the return value, but there was no such thing as functions that
were declared as not returning a value. In such an environment, it would
be natural for main() to return an int, even if there was no particular
need to; and there was certainly a need for some mechanism whereby a
child process could report simple status information to its parent.
Agreed.


I wouldn't ridicule void main(); the only thing I've ever done is point
out that it's not supported by the standard. If it were added to the
list of permitted signatures for main(), I'd have no objection.

I feel that after writing a bunch of test programs for library API
functions, it feels a little bit tedious to include the same
boilerplate code to #include <stdlib.h> and return EXIT_SUCCESS for
every example to try to keep everything kosher when all the example
does is demonstrate how to use the function. There is no intent to
design it to communicate in any meaningful way with other programs
through the OS. A 'void main(...)' could communicate this intent in
as a don't care to a hosted environment (semantics could be that the
program implicitly returns EXIT_SUCCESS no matter what). This would
be in contrast to a program defined with 'int main(...)' whose intent
would be "Ya better look at my return code, cause you're going to need
it." It just feels like 'void main(...)' could add a useful semantic
to programs even in hosted mode. There are probably reasons for not
doing it; I'm just not familiar enough with the history to understand
why.

Thanks to everyone else that's responded; it's helped clarify things.

Best regards,
John D.
 
J

James Kuyper

On 11/04/2011 11:00 PM, ImpalerCore wrote:
....
Yes, you're correct. I just used "pipes" in the sense of "one program
communicating with another". It was phrased poorly. Is there a
better term?

"interprocess communication"? A process is a particular instantiation of
a program, and it's processes which are communicating, not programs.
I feel that after writing a bunch of test programs for library API
functions, it feels a little bit tedious to include the same
boilerplate code to #include <stdlib.h> and return EXIT_SUCCESS

In that case, just return 0; then you don't need <stdlib.h>.
 
I

ImpalerCore

On 11/04/2011 11:00 PM, ImpalerCore wrote:
...


"interprocess communication"? A process is a particular instantiation of
a program, and it's processes which are communicating, not programs.


In that case, just return 0; then you don't need <stdlib.h>.

That will clean up quite a bit of extraneous code, thanks!
 
K

Kaz Kylheku

Yes, you're correct. I just used "pipes" in the sense of "one program
communicating with another". It was phrased poorly. Is there a
better term?


Yeah, I seem to keep forgetting that.


I feel that after writing a bunch of test programs for library API
functions, it feels a little bit tedious to include the same
boilerplate code to #include <stdlib.h> and return EXIT_SUCCESS for
every example to try to keep everything kosher when all the example

If you're inconvenienced by producing numerous test programs which
are similar to each other, that's a situation where you can apply some
code generation techniques.

The first tool to try would be the preprocessor.

Your boilerplate can be reduced to

#define SOME_MACROS_CONFIGURING_BOILERPLATE
#include "boilerplate.h"

Chances are it's not just the #include <stdlib.h> and EXIT_SUCCESS
that is repeated from tst to test.
 
I

Ian Collins

If you're inconvenienced by producing numerous test programs which
are similar to each other, that's a situation where you can apply some
code generation techniques.

The first tool to try would be the preprocessor.

Your boilerplate can be reduced to

#define SOME_MACROS_CONFIGURING_BOILERPLATE
#include "boilerplate.h"

Or use a unit testing framework.
 
I

ImpalerCore

If you're inconvenienced by producing numerous test programs which
are similar to each other, that's a situation where you can apply some
code generation techniques.

The first tool to try would be the preprocessor.

Your boilerplate can be reduced to

  #define SOME_MACROS_CONFIGURING_BOILERPLATE
  #include "boilerplate.h"

Chances are it's not just the #include <stdlib.h> and EXIT_SUCCESS
that is repeated from tst to test.

In my case, the example programs are more for users to demonstrate the
data structure and API in a sample task. Eventually I'd like to get
more formal unit testing built, but I'm not there yet.

Here is an example of the kinds of examples I'm writing:

\code snippet
#include <stdio.h>
#include <stdlib.h>
#include <clover/config.h>
#include <clover/macros.h>
#include <clover/constraint.h>
#include <clover/strops.h>

int pstrnatvcmp( const void* p, const void* q );

int main( void )
{
size_t idx;

char* files[] =
{
"file02.txt",
"file120.txt",
"file12.txt",
"file02a.txt",
"file1.txt",
"file05.txt",
"file4.txt",
"file100.txt",
"file.txt",
"file121.txt",
"file7.txt",
"file100b.txt",

"file.3",
"file.00",
"file.02",
"file.010",
"file",
"file.11",
"file.1",
"file.001",
"file.125",
"file.002",
"file.3000",
};

printf( "---File Listing---\n" );
for ( idx = 0; idx < C_ARRAY_N (files); ++idx ) {
puts( files[idx] );
}
printf( "\n" );

qsort( files, C_ARRAY_N (files), sizeof (char*), c_pstrvcmp );

printf( "---File Listing (Lexicographical)---\n" );
for ( idx = 0; idx < C_ARRAY_N (files); ++idx ) {
puts( files[idx] );
}
printf( "\n" );

qsort( files, C_ARRAY_N (files), sizeof (char*), pstrnatvcmp );

printf( "---File Listing (Natural Ordering)---\n" );
for ( idx = 0; idx < C_ARRAY_N (files); ++idx ) {
puts( files[idx] );
}

return EXIT_SUCCESS;
}

int pstrnatvcmp( const void* p, const void* q )
{
c_return_value_if_fail( p != NULL, -( p != q ) );
c_return_value_if_fail( q != NULL, p != q );

return c_strnatcmp( *((const char**)p), *((const char**)q) );
}
\endcode

There's not a lot of boilerplate in these kinds of "use-case"
examples, but I found that repeatedly including <stdlib.h> just for
EXIT_SUCCESS in these kinds of example programs felt tedious, since
they are more for show than as a component one would want to use in a
script.

When I come to the point of writing more formal testing, I'm sure
those techniques will come in handy.

Best regards,
John D.
 
M

Malcolm McLean

In all the goofy attempts to "prove" that an incorrect
main() return value can kill people, the goofballs have
relied on somebody NEGLIGENTLY grabbing some little
piece of software bascially at random and sticking it
willy-nilly into a script and/or system, then expressing
mock amazement when it doesn't do what it was never warranted
to do, and hundreds/thousands/millions/billions of
imaginary people died as a result.  (My goofball example
was that NORAD uses a "Hello World" program to determine
whether to launch nuclear missiles; hey, "Hello World"
printed successfully, but the program returns EXIT_FAILURE,
LAUNCH!!!)
If you have a safety-critical program, then you have belt, braces and
underpants. void main() may snap the belt - someone grabs a little
utility and assumes that it will return a failure condition if it
doesn't succeed. However the braces should work - he passes the code
to a fellow programmer, who checks it. But maybe the documentation for
the program is missing, it's Friday night, the pub is beckoning. We've
still got the underpants - QA will throw a full test suite at the
script, and ensure that every line is executed at least once. So it
should pick up that the error condition can never be triggered. But
once we're down to the underpants, we're in a very uncomfortable
position. QA will rightly ask how a potentially lethal script came so
close to shipping.
 
L

Lew Pitcher

Surely you know better than to start a sentence like that around
here... ;-)

From it's first incarnation in roughly 1964, S/360 DOS, the "small"
mainframe OS, and its descendents DOS/VS, DOS/VSE, VSE/SP, VSE/ESA and
zVSE had no return codes dealt with by the OS. They were adding in
about 1990 (give-or-take - it may have been the first release of
VSE/ESA).

Returncodes were introduced in VSE/ESA, as I recall.

As a System Programmer for a number of VSE/ESA systems, I wrote a little
utility that permitted COBOL programs to properly return returncodes to the
OS (IBM's native COBOL compiler and runtime didn't have that ability yet)
so that conditional JCL could evaluate the results and take appropriate
action.
There was no good way to have conditional JCL either
(although there were always hacks).

Conditional JCL was available, but only useful if the program could return a
condition code. That restricted it to IBM utilities and /some/ programs
written in C (not IBM's C compiler, IIRC)
This was always a huge omission, and the other mainframe OS's didn't
suffer the same limitation.

[snip]
 
L

Lew Pitcher

Returncodes were introduced in VSE/ESA, as I recall.

Correction. It was DOS/VSE. VSE/ESA introduced native returncodes to the
compiler suite IIRC
As a System Programmer for a number of VSE/ESA systems, I wrote a little
utility that permitted COBOL programs to properly return returncodes to
the OS (IBM's native COBOL compiler and runtime didn't have that ability
yet) so that conditional JCL could evaluate the results and take
appropriate action.
There was no good way to have conditional JCL either
(although there were always hacks).

Conditional JCL was available, but only useful if the program could return
a condition code. That restricted it to IBM utilities and /some/ programs
written in C (not IBM's C compiler, IIRC)
This was always a huge omission, and the other mainframe OS's didn't
suffer the same limitation.

[snip]

--
Lew Pitcher
Master Codewright & JOAT-in-training | Registered Linux User #112576
Me: http://pitcher.digitalfreehold.ca/ | Just Linux: http://justlinux.ca/
---------- Slackware - Because I know what I'm doing. ------
 
S

Seebs

Surely you know better than to start a sentence like that around
here... ;-)

I was sort of hoping to find one, because I love to know strange
counterexamples.

-s
 
P

Phil Carmody

Kaz Kylheku said:
That's true,

except when it's false. Userspace helpers, anyone?
but in the C language there is a sense of "use" which
means simply to look at a value (even to just propagate it elsewhere).

An adequate answer to the question is that the OS (or whatever it is that calls
your program) uses the return value in order to establish a termination status
(which is something related to that value, but is not that value).

Other programs then use the termination status.

If you're ignoring userspace helpers, then that indeed is better description of
what happens.

Phil
 
B

Barry Schwarz

"io_x" <[email protected]> ha scritto nel messaggio

when i see my routines i note this


it seem to me that if someone insert stringInput<EOF>
than stringInput is a valid line even if not has "\n"
the above function not get that string
because return NULL on that input

Check 7.19.7.2-3 again. Look at the second sentence carefully.
there is someone out there? It seem the only response to my posts
is by Ian Collins that i plonk some time ago [i see that trhu
google groups] so i can not answer

possibly *i'm* in the KF of many in the group for my strange
behaviour too
if i can say the true i like not to be 100% grammar correct...
it is strange to think that?

It's not your grammar.
 
J

James Kuyper

there is someone out there? It seem the only response to my posts
is by Ian Collins that i plonk some time ago [i see that trhu
google groups] so i can not answer

possibly *i'm* in the KF of many in the group for my strange
behaviour too

For me, his strangeness isn't the problem. The uselessness of giving him
correct advice is the main reason, with the unreadability of both his
code and his English being strong contributing factors.
It's not your grammar.

True - but actively liking incorrect grammar is not a good idea for a
computer programmer, so I would have to agree that it is strange to
think that way.
 
T

Tim Rentsch

Keith Thompson said:
[snip preliminary]

My own preference would have been to make defining "main" incorrectly
a constraint violation, not undefined behavior. [snip elaboration]

Personally I think this is a bad idea, both for practical
reasons and principled ones.

The principle-motivated reason is this: making something
a constraint violation is a bad idea unless it's really
clear that it never makes sense under any circumstances.
There are plenty of gray areas (the definition of main()
being one) where it often makes sense to give a diagnostic
but the decision to do so is better left to the implementation
as a QOI concern.

The practical reasons are these. First, an implementation may
not always know exactly which forms of main() might happen to
work on a particular target platform (and there certainly are
compilers that support multiple-but-still-similar target
platforms). Second, mandating ever more cases of required
diagnostics only increases the incentive for an implementation
to have its default mode be a non-conforming one. Requiring
a diagnostic for the UB forms of main() may win a small battle
but lose a larger and much more important battle. The risk
(large) is not worth the return (small).
 
T

Tim Rentsch

zemir said:
system("cls");
float r=0; int c;
printf("This programm calculate Area and Perimenter of a Circle");
printf("\n\nPlease insert radius value (in cm): ");

fflush( stdout );
scanf("%f", &r);
if( r > 0 ) {

If scanf() didn't find anything that looked like a number 'r' now has
some random value. [snip unrelated]

I don't think so. Either there is a well-formed number (or INF,
etc) on input, and the scanf() call yields the value 1, or there
wasn't a well-formed number on input, and the scanf() call yields
the value 0, which means 'r' was not assigned to. So if scanf()
didn't find something that looks like a number, 'r' still has the
value 0.
 
J

Joe keane

Second, mandating ever more cases of required diagnostics only
increases the incentive for an implementation to have its default mode
be a non-conforming one.

But it's just enforcing the rules that apply to every other function.

hdr1.h:
int f(int *x, float y);

hdr2.h:
int f(long *x, double y);

We know that is going to cause trouble, why not warn about it?
 
F

Fritz Wuehler

Oh I don't think they need any more incentive than they already have. gcc is
a prime example of non-conformance for non-conformance's sake.
But it's just enforcing the rules that apply to every other function.

hdr1.h:
int f(int *x, float y);

hdr2.h:
int f(long *x, double y);

We know that is going to cause trouble, why not warn about it?

C is a shitty unsafe language designed to be easy to implement. It's been
applied so inappropriately for so long, no amount of "standardizing" is ever
going to fix that. Given the miserable state of the "art" (gcc) it seems
ill advised to add more bloat and complexity to what is already a house of
cards on the verge of "The Big One". Better to keep static analysis tools
like various version of lint, splint etc. up to date with (optional?)
regression test-like suites of checks for known, bad code. Typical Linux
apps already compile with hundreds or thousands of warnings and everybody
already ignores them. Very few people are inclined to write correct, safe
code and of those people only a small percentage of them use C. There are
better things to spend your time on than improving C compiler diagnostics.
External tools are a better value and probably offer a better platform to do
those checks from than inside a burning house like gcc. I realize there are
plenty of C compilers around but unfortunately gcc is the most popular and
so are forks of it.
 
M

Markus Wichmann

Oh I don't think they need any more incentive than they already have. gcc is
a prime example of non-conformance for non-conformance's sake.


C is a shitty unsafe language designed to be easy to implement. It's been
applied so inappropriately for so long, no amount of "standardizing" is ever
going to fix that.

Care to elaborate? What do you mean by unsafe and inappropriate?
Given the miserable state of the "art" (gcc) it seems
ill advised to add more bloat and complexity to what is already a house of
cards on the verge of "The Big One". Better to keep static analysis tools
like various version of lint, splint etc. up to date with (optional?)
regression test-like suites of checks for known, bad code.

Well, you could also try to do formal verification, but that takes much
work.
Typical Linux
apps already compile with hundreds or thousands of warnings and everybody
already ignores them.

Well, the thing is, a warning means "you wrote valid C code, but you
used a construction that does something you probably don't want".
However, sometimes I actually know what I'm doing and I refuse to change
my code for the sake of removing a warning if it destroys clarity.
Very few people are inclined to write correct, safe
code and of those people only a small percentage of them use C. There are
better things to spend your time on than improving C compiler diagnostics.

Well, there's the hen-and-egg problem: Actually, nearly every program
contains C code, be it in form of the compiler or the interpreter of
something not C. The only exceptions I know of are freepascal, which is
written in Pascal, and g++, which is written in C++. And even those use
the libc, which is written in C.
External tools are a better value and probably offer a better platform to do
those checks from than inside a burning house like gcc. I realize there are
plenty of C compilers around but unfortunately gcc is the most popular and
so are forks of it.

Well, you can make your checks part of the compiler or leave the stuff
standalone, I don't see much difference there. Just that it might be
easier to check an AST instead of a raw .c file and you might just take
advantage of the fact that gcc has to build it!

Ciao,
Markus
 
Z

Zoltan Kocsi

C is a shitty unsafe language designed to be easy to implement. It's been

C is a language originally designed to replace assembly as the primary
language for writing operating systems. Therefore, it allows you to get down
to the bare metal and deal with things what assembly gives you access to,
such as addresses, words, bits and the like. C has achieved that goal and is a
brilliant language when you have to write code for an embedded system where
you have a processor and a bucketload of peripherals but what you do not have
is gigabytes of virtual memory, clock cycles to waste and, most importantly, a
hardware budget to whack in a processor which is $10 dearer and add an other
$10 worth of memory. The fact that one can use C to write application
programs on a full-blown operating system environment is an added bonus.

Programming languages are tools and C is a very pointy and sharp tool, but in
a craftsman's hand it can create beautiful things. If you need type safety
and objects and abstract concepts and all the other stuff, you should use some
other language; there are plenty around.
Typical Linux apps already compile with hundreds or thousands of warnings
and everybody already ignores them. Very few people are inclined to write
correct, safe code and of those people only a small percentage of them use
C.

I am one of them. I turn on all warnings on the compiler and make all
warnings errors. The thing is, most of the warnings you eliminate that way
are warnings which are the result of lazyness. You assign a pointer to a
pointer of different kind because you know that that's OK. The compiler
screams. All you have to do is to put a cast there and it keeps its mouth
shut. The result is better code because whoever reads it (including myself,a
few years down the track) can see the intention to change the pointer from
type X to type Y. There's hopefully a comment nearby explaining why that's
happening. The rest of the warnings are usually an indication of real
programming dangers (i.e. bugs which will byte you later).
[...] inside a burning house like gcc. I realize there are plenty of C
compilers around but unfortunately gcc is the most popular and so are forks
of it.

The problem with gcc is that it is not just a C compiler. It is a C compiler
and a C++ compiler and an Ada compiler and a Fortran compiler and a java
compiler and a whatnotelse compiler, which should run on any machine
whatsoever, compiling for any processor and operating system combination.

In that regards, gcc achieves a helluva lot, despite all its quirks. Yes, it
has a few extremely stupid things, yes, it sometimes generates inefficient
code (and on, admittedly very rare, occasions even incorrect code), but it
has some advantages, especially when you have to deal with multiple targets.

In addition, gcc has a lot of historical baggage which is very hard to clean
up unless you are willing to start from scratch. However, there are attempts
to rectify gcc's problems, the most prominent being LLVM + clang; time will
tell if that will succeed.

Zoltan
 

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
474,079
Messages
2,570,574
Members
47,207
Latest member
HelenaCani

Latest Threads

Top