Understanding a simple C program

C

CJ

Hi Friends -

This is a pretty naive question, so be patient. I have been thinking
about the following simple C program:

void main()
{
int a, b, c;
a=1;
b=2;
c=a+b;
printf("%d", c);
}

Let's imagine what the compiler does to this program. Probably it says
"OK, so here we have some variables. We've got an accumulator and some
registers X and Y hanging around. Let's do something like:
LDA 0x1 # put 1 into accumulator
LDX 0x2 # put 2 into register X
ADC X # add register X to accumulator with carry
for example."

So let's fast forward a bit, and our program is executing. But now maybe
we've got A and X set up, but before we get chance to execute the add
instruction, we get interrupted and the kernel passes control to some
other process. In this case, probably all the registers will have to be
pushed onto the stack, then popped back when control returns to our
process.

But haven't we then lost all the benefits of using registers for storage
instead of RAM?

Or to put it another way, why do people worry so much about hitting
cache and optimizing register usage, when this can easily (and probably
will be) wiped out by control passing to another process in the mean
time?
 
W

Walter Roberson

Or to put it another way, why do people worry so much about hitting
cache and optimizing register usage, when this can easily (and probably
will be) wiped out by control passing to another process in the mean
time?

The difference is in the "probably will be". Time slices on multiuser
systems are usually long enough to get some serious work done
when the data is in cache. If the data has to be swapped from
hard-disk, on the other hand, a single time-slice might not be enough
to finish the swap in (an OS would usually swap into RAM in such
a case, making the RAM available when the process is restarted.)
 
U

user923005

Hi Friends -

This is a pretty naive question, so be patient. I have been thinking
about the following simple C program:

void main()
{
int a, b, c;
a=1;
b=2;
c=a+b;
printf("%d", c);

}

Let's imagine what the compiler does to this program. Probably it says
"OK, so here we have some variables. We've got an accumulator and some
registers X and Y hanging around. Let's do something like:
LDA 0x1 # put 1 into accumulator
LDX 0x2 # put 2 into register X
ADC X # add register X to accumulator with carry
for example."

So let's fast forward a bit, and our program is executing. But now maybe
we've got A and X set up, but before we get chance to execute the add
instruction, we get interrupted and the kernel passes control to some
other process. In this case, probably all the registers will have to be
pushed onto the stack, then popped back when control returns to our
process.

But haven't we then lost all the benefits of using registers for storage
instead of RAM?

Or to put it another way, why do people worry so much about hitting
cache and optimizing register usage, when this can easily (and probably
will be) wiped out by control passing to another process in the mean
time?

Aside:
Your C program has several bugs in it.

As far as the actual processes by which the C program executes on a
particular operating system goes, the C language makes no distinction
or explanation. The language itself is a somewhat abstract
representation of *what* a program should do and not *how* it should
do it.

The C language does not specify what happens when a thread switches
context (and indeed it may run in an environment that has only a
single thread of execution like an IC in a toaster).

If you want specific answers about specific compilers and operating
systems in concert, you may be able to get some sort of answers in a
compiler or OS specific newsgroup.
 
R

Richard

user923005 said:
Aside:
Your C program has several bugs in it.

He is missing an include and had form of main which will work on 99.999%
of the worlds compilers. Bad form yes. But for the sake of his question
these points are totally and utterly irrelevant and I doubt they will
affect the flow he explained for the sake of his question - a little OT
for sure .....
As far as the actual processes by which the C program executes on a
particular operating system goes, the C language makes no distinction
or explanation. The language itself is a somewhat abstract
representation of *what* a program should do and not *how* it should
do it.

However, C is used in situations where the programmer is indeed
interested in performance and keeping caches happy.

Google certainly has a lot of information about it.

To the OP: don't underestimate the amount of time given to a time slice
it might well execute hundreds or thousands of instructions and the
switch overhead is tiny in comparison.

The actual mechanism used to save the registers (whether stack or in
shadow registers on in onboard cache or .....) would certainly vary a
lot I would think and this is not the place to discuss that.
 
S

santosh

CJ said:
Hi Friends -

This is a pretty naive question, so be patient. I have been thinking
about the following simple C program:

void main()

As per Standard C main must return an int to be portable. Returning
void, if it exists, has to be implementation defined.
{
int a, b, c;
a=1;
b=2;
c=a+b;
printf("%d", c);

Calling a function without a prototype in scope is asking for trouble,
especially a variadic function like printf. It's prototype is included
in stdio.h

<snip about registers, context switching, cache etc.>

Why not ask this in a group like comp.arch or alt.lang.asm? The C
language does not have anything to say about registers, cache or code
speed.
 
E

Eric Sosman

CJ wrote On 10/22/07 17:47,:
Hi Friends -

This is a pretty naive question, so be patient. I have been thinking
about the following simple C program:

void main()
{
int a, b, c;
a=1;
b=2;
c=a+b;
printf("%d", c);
}

Let's imagine what the compiler does to this program. Probably it says
"OK, so here we have some variables. We've got an accumulator and some
registers X and Y hanging around. Let's do something like:
LDA 0x1 # put 1 into accumulator
LDX 0x2 # put 2 into register X
ADC X # add register X to accumulator with carry
for example."

So let's fast forward a bit, and our program is executing. But now maybe
we've got A and X set up, but before we get chance to execute the add
instruction, we get interrupted and the kernel passes control to some
other process. In this case, probably all the registers will have to be
pushed onto the stack, then popped back when control returns to our
process.

But haven't we then lost all the benefits of using registers for storage
instead of RAM?

Or to put it another way, why do people worry so much about hitting
cache and optimizing register usage, when this can easily (and probably
will be) wiped out by control passing to another process in the mean
time?

You haven't really asked a C question, so instead
of answering I'll offer some counter-questions:

1) How many interrupts per second does your computer
handle? (Measure.)

2) What is the average time between interrupts?

3) How many instructions can your computer execute
in the interval (2)? (Guess if you must, measure
if you can.)

4) In light of (3), what is the probability that an
interrupt will occur between the time LDA starts
and ADC finishes?

5) And by the way: Have you ever heard of "register
windows?"
 
C

CJ

Aside:
Your C program has several bugs in it.

Ha ha, very funny.
As far as the actual processes by which the C program executes on a
particular operating system goes, the C language makes no distinction
or explanation. The language itself is a somewhat abstract
representation of *what* a program should do and not *how* it should
do it.

The C language does not specify what happens when a thread switches
context (and indeed it may run in an environment that has only a
single thread of execution like an IC in a toaster).

If you want specific answers about specific compilers and operating
systems in concert, you may be able to get some sort of answers in a
compiler or OS specific newsgroup.

Jeez, I was asking about what might happen in practise, not for an
answer general enough to work for a toaster!
 
C

CJ

You haven't really asked a C question, so instead
of answering I'll offer some counter-questions:

1) How many interrupts per second does your computer
handle? (Measure.)

I don't know how to measure... I'll take a guess: 60.
2) What is the average time between interrupts?

1/60 sec.
3) How many instructions can your computer execute
in the interval (2)? (Guess if you must, measure
if you can.)

700 MHz = 700 000 000 ops per sec = roughly 10 000 000 ops per interval
(2).
4) In light of (3), what is the probability that an
interrupt will occur between the time LDA starts
and ADC finishes?

Well, quite small! But I bet you're going to tell me my figures are way
off.
5) And by the way: Have you ever heard of "register
windows?"

No...
 
S

santosh

CJ said:
Jeez, I was asking about what might happen in practise, not for an
answer general enough to work for a toaster!

In practise caches _do_ make a big difference, otherwise manufacturers
would use them, seeing as they use quite expensive memory.

When a program is interrupted it is very likely that the caches become
useless, but immediately, at the next miss, which is likely to be the
instruction following the interrupt, the caches will be automatically
reloaded, again improving execution speed.

Caches work on the principle that there are likely to be more cache hits
than cache misses. And empirically, it does work effectively under most
circumstances.
 
A

Al Balmer

Jeez, I was asking about what might happen in practise, not for an
answer general enough to work for a toaster!

That's exactly why you should ask the question in an OS or compiler
specific newsgroup. What *might* happen in practice could be most
anything, depending on the implementation. Some people do write code
for toasters :)

Even in a given environment, you may find the answer to be "it
depends."
 
U

user923005

Ha ha, very funny.

Mistake 1:
void main()
From the C FAQ:
1.25b: What's the right declaration for main()?
Is void main() correct?

A: See questions 11.12a to 11.15. (But no, it's not correct.)

11.12a: What's the correct declaration of main()?

A: Either int main(), int main(void), or int main(int argc,
char *argv[]) (with alternate spellings of argc and *argv[]
obviously allowed). See also questions 11.12b to 11.15 below.

References: ISO Sec. 5.1.2.2.1, Sec. G.5.1; H&S Sec. 20.1 p.
416; CT&P Sec. 3.10 pp. 50-51.

11.12b: Can I declare main() as void, to shut off these annoying
"main returns no value" messages?

A: No. main() must be declared as returning an int, and as
taking either zero or two arguments, of the appropriate types.
If you're calling exit() but still getting warnings, you may
have to insert a redundant return statement (or use some kind
of "not reached" directive, if available).

Declaring a function as void does not merely shut off or
rearrange warnings: it may also result in a different function
call/return sequence, incompatible with what the caller (in
main's case, the C run-time startup code) expects.

(Note that this discussion of main() pertains only to "hosted"
implementations; none of it applies to "freestanding"
implementations, which may not even have main(). However,
freestanding implementations are comparatively rare, and if
you're using one, you probably know it. If you've never heard
of the distinction, you're probably using a hosted
implementation, and the above rules apply.)

References: ISO Sec. 5.1.2.2.1, Sec. G.5.1; H&S Sec. 20.1 p.
416; CT&P Sec. 3.10 pp. 50-51.
{
int a, b, c;
a=1;
b=2;
c=a+b;

Mistake 2:
printf("%d", c);

A varadic function 'printf()' is called without the presence of a
prototype. This results in undefined behavior.

Mistake 3:
missing:
return 0;
because you were not aware that main() returns int.

[snip]
 
J

Joachim Schmitz

user923005 said:
Ha ha, very funny.

Mistake 1:
void main()
From the C FAQ:
1.25b: What's the right declaration for main()?
Is void main() correct?

A: See questions 11.12a to 11.15. (But no, it's not correct.)

11.12a: What's the correct declaration of main()?

A: Either int main(), int main(void), or int main(int argc,
char *argv[]) (with alternate spellings of argc and *argv[]
obviously allowed). See also questions 11.12b to 11.15 below.

References: ISO Sec. 5.1.2.2.1, Sec. G.5.1; H&S Sec. 20.1 p.
416; CT&P Sec. 3.10 pp. 50-51.

11.12b: Can I declare main() as void, to shut off these annoying
"main returns no value" messages?

A: No. main() must be declared as returning an int, and as
taking either zero or two arguments, of the appropriate types.
If you're calling exit() but still getting warnings, you may
have to insert a redundant return statement (or use some kind
of "not reached" directive, if available).

Declaring a function as void does not merely shut off or
rearrange warnings: it may also result in a different function
call/return sequence, incompatible with what the caller (in
main's case, the C run-time startup code) expects.

(Note that this discussion of main() pertains only to "hosted"
implementations; none of it applies to "freestanding"
implementations, which may not even have main(). However,
freestanding implementations are comparatively rare, and if
you're using one, you probably know it. If you've never heard
of the distinction, you're probably using a hosted
implementation, and the above rules apply.)

References: ISO Sec. 5.1.2.2.1, Sec. G.5.1; H&S Sec. 20.1 p.
416; CT&P Sec. 3.10 pp. 50-51.
{
int a, b, c;
a=1;
b=2;
c=a+b;

Mistake 2:
printf("%d", c);

A varadic function 'printf()' is called without the presence of a
prototype. This results in undefined behavior.

Mistake 3:
missing:
return 0;
because you were not aware that main() returns int.
mistake 4: missing \n in printf causing undefined behavoir

Bye, Jojo
 
M

Martien Verbruggen

mistake 4: missing \n in printf causing undefined behavoir

No, it doesn't.

It's implementation defined whether a newline as the last character is
needed or not. It's most portable to always use one.

Martien
 
P

pete

Martien said:
No, it doesn't.

It's implementation defined whether a newline as the last character is
needed or not.

The shown code is undefined.

Whether or not source code is undefined,
is a property of the source code.
 
M

Martien Verbruggen

The shown code is undefined.

I wasn't disputing that. I was only disputing the 'mistake 4' comment.
Whether or not source code is undefined,
is a property of the source code.

Euhmm.. yes. However, whether or not mistake 4 _causes_ undefined
behaviour is a property of what is described as mistake 4. not of the
rest of the code.

Martien
 
P

pete

Martien said:
I wasn't disputing that. I was only disputing the 'mistake 4' comment.


Euhmm.. yes. However, whether or not mistake 4 _causes_ undefined
behaviour is a property of what is described as mistake 4. not of the
rest of the code.

Mistake 4 is the only part of the code that I'm talking about.

Even if there were no other mistakes,
mistake 4 would be sufficient to prevent the program from
being a "correct program".

Whatever behavior results from running that program,
is not defined between the code and the standard.
Even if the behavior is unremarkable, it's still undefined.
 
U

user923005

Mistake 4 is the only part of the code that I'm talking about.

Even if there were no other mistakes,
mistake 4 would be sufficient to prevent the program from
being a "correct program".

Whatever behavior results from running that program,
is not defined between the code and the standard.
Even if the behavior is unremarkable, it's still undefined.

It isn't undefined because the implementation *has* to define it.
It's in the same category as what the maximum value of a double is.

I also think it is a mistake, but the mistake does not result in
undefined behavior. It results in implementation defined behavior.
(That behavior might be that 'Hello world!' does not show up on stdout
and that might not be the _expected_ behavior, but it is acceptable if
that is what the implementation says it should do).
 
L

Larry__Weiss

user923005 said:
A varadic function 'printf()' is called without the presence of a
prototype. This results in undefined behavior.

Does the side-effect of the undefined behavior have to wait to manifest
itself until that statement's time has come to be executed?

- Larry
 
M

Martien Verbruggen

Martien Verbruggen wrote:

[removal of various levels of quotation]
Mistake 4 is the only part of the code that I'm talking about.

Even if there were no other mistakes,
mistake 4 would be sufficient to prevent the program from
being a "correct program".

Whatever behavior results from running that program,
is not defined between the code and the standard.
Even if the behavior is unremarkable, it's still undefined.

Can you quote the bit from the standard that says that it produces
undefined behaviour?

My understanding is, as I have already stated, that it is implementation
defined, not undefined. I base that opinion on 7.19.2-2 in the c99
standard, which defines a text stream, and states:

Whether the last line requires a terminating new-line character is
implementation-defined.

I believe the C90 standard has similar, if not identical, wording.
Previous discussions on this group tend to agree that the above wording
also means that whether a prorgam needs to end its (textual) output with
a newline is implementation defined, not undefined.

Martien
 

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,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top