What is a stack frame?

P

polas

Recently K. Thompson asked me to specify what did I understand
with "stack frame". I think that it is important for people using the
language, that this important concept is clear, so I have produced
this post.

Obviously, there are some people here that live in some
special world where all those things do not exist. They should stay
there and ignore this thread. The same goes for all people that
program in coffee machines and other primitive processors that
do not have a stack pointer or enough registers to use a stack
efficiently.

----------------------
What is a stack frame?
---------------------

Within a stack, each procedure builds a "stack frame", i.e. a fixed
point within the stack, where storage for local variables is reserved.

The stack frames are a linked list of this storage areas. This builds
the conceptual stack that the language needs. Each stack frame is
linked to the previous one. A new stack frame is built when a
procedure is entered, and it is popped from the stack of stack frames
at the procedure exit.

The "main" function has the first (visible) frame, and each stack
frame built in one of the functions called after "main" started is
linked to that stack frame. This makes it possible for debuggers
and other tools to "walk the stack" of called procedures.

We can (conceptually) describe the stack frame as follows

struct tagStackFrame {
void *Previous;
void *ThisFrame;
char LocalStorage[];

};

The "Next" member is a pointer to the previous function's stack frame,
the "ThisFrame" pointer points to the current stack frame, and the
Local storage is made out of different variables that a function
declares. We can imagine that the local storage of deeper nested
blocks is added to the function's local storage, even if it is not
visible (i.e. can't be accessed) after its scope disappears.

Building the stack frame (Prologue)
------------------------

The first thing to do to build a stack frame is then, to save in the
"Previous" slot, the value of the current stack frame pointer.
Normally, this is a dedicated register. For instance, the power PC
version of lcc-win uses register 31. This register is normally
defined by the operating system ABI (Application Binary Interface)
since ALL programs running in the same operating system MUST agree
as to what the frame pointer register is, if not CHAOS is surely the
consequence.

After saving the current FP (or Frame Pointer) we copy the current
value of the stack pointer into the frame pointer, to establish a new
stack frame from this stack position on. We fill then, the "ThisFrame"
slot of our structure, and lastly, we subtract from the frame pointer
the amount of space needed by the function's local variables, our
"Local storage" member.

There exist MANY variation of this scheme. Some optimizing compilers
save themselves the trouble of having a different register than the
stack pointer to build a stack frame, and just use the stack pointer to
address local variables. Conceptually however, nothing changes.

Popping the stack frame (Epilogue)
-----------------------

A function must restore the previous stack frame before leaving the
scene. This is done in the reverse order of the previous actions. The
space allocated for local variables is released by adjusting the stack
pointer, the previous value of the stack frame is popped from the stack,
and the function can now return.

Other stuff
-----------

A function uses registers to do its calculations. Those register can be
scratch registers, i.e. registers that can be used without having to
save them, or they can be registers that need save before use, to
preserve their value across function calls. The saved registers are part
of the stack frame, even if I did not mention that above to keep things
simple. They are restored before the current function exits.

Another thing to be known is "alloca". This function allocates storage
from the stack by decreasing the stack pointer. A function that calls
alloca must do the popping of the current stack frame in a different
manner since the current stack frame is "deformed" by alloca.

And yet another thing: in standard C, we can have objects whose size
is allocated in local storage but whose exact size is unknown until
run time. This is similar to alloca, and produces the same consequences.

As a C programmer who tends to read this newsgroup a lot more than
post to it I have found this topic very useful to me - without any
formal C training these basic underlying concepts are often missed...
so thanks for that definition - even if it is considered off topic by
some people or whatevern, it has helped me.

A very quick question (possibly slightly off topic - sorry if so)
apart from the stack pointers for functions, and the heap for dynamic
allocation, is there any other memory storage commonly used and
refered to?

Nick
 
S

santosh

polas wrote:

A very quick question (possibly slightly off topic - sorry if so)
apart from the stack pointers for functions, and the heap for dynamic
allocation, is there any other memory storage commonly used and
refered to?

Yes. Static objects are likely (though the standard says nothing about
this) to be stored in the program's data segment, which may or may not
be different from it's heap. Also read-only data might be stored in a
special write protected segment. You also forgot that a disk file is
also a storage unit. Many old DOS programs used to swap portions of
their image to the disk on the fly. Nowadays the OS does this instead.
 
C

Chris Torek

We can (conceptually) describe the stack frame as follows

struct tagStackFrame {
void *Previous;
void *ThisFrame;
char LocalStorage[];
};

There is no need for a separate "this frame" pointer.

In any case, the more general term, which covers what a C compiler
*must* provide in effect, is "activation record". Any given
"activation record" -- whether it is on a stack or not -- contains
the "local storage" for the function, as you describe here.

If the activation record contains a single pointer to a previous
("I was called from") record, and new entries are only ever added
and removed from the same end, this forms a "stack-like" data
structure: a linked list in which the "most current" end is the
"top" of the stack, and previous entries are exposed by "popping
off" that top. (The top may well be at the lowest physical or
virtual address, or there may be no defined ordering. The
latter occurs when allocating activation records from a general
storage arena, a la malloc().)
The "Next" member is a pointer to the previous function's stack frame,

"Previous" is of course a better name for this (and is the one
you used earlier). :)

It is also true that many, perhaps even most, C compilers use a
hardware-provided stack to implement the stack-like data structure
containing the necessary activation records. These are "stack
frames". At least one compiler (for IBM machines) did historically
use a general free-store arena: there is no hardware-provided stack,
so the compiler can do whatever it likes. (As far as I know, these
systems still do this. I merely have not used them in more than
twenty years.)

It is better (in my opinion) to stick with the general term, i.e.,
"activation record", in comp.lang.c, since C compilers are not
required to use a hardware stack to implement the implied stack of
activation records. Moreover, systems that provide threads have
multiple sets of activation records, which may or may not form a
branching data structure of the sort sometimes referred to as a
"cactus stack". (You should think of a saguaro cactus here, the
stereotypical ones seen in Road-Runner-and-Coyote cartoons.) Of
course, A C implementation need not even use actual activation
records -- for instance, a magic one might use Divine Inspiration
to produce the correct results -- but it must behave *as if* it
used them. It does not, however, have to behave as though these
were allocated from a single hardware-provided stack, which is
good news for those that support threads.

You also mentioned VLAs (which are new in C99) here, and I have
a few more notes to make about this and your other extra notes:
A function uses registers to do its calculations.

Most (I think; certainly at least "many") do. Not all, though.
The AT&T "Hobbit" architecture, for instance, had no registers of
that sort at all. Instead, it used "top of stack" for everything.
As a simplified example, if you wanted to add "x" and "y", instead
of loading the values into registers and doing an "add r1, r2",
you would push the values of x and y and then do an "add", which
pops the two top values from the value stack, adds them, and pushes
the result on the stack. Some Forth-oriented hardware does this
as well.
Those register can be scratch registers, i.e. registers that can
be used without having to save them, or they can be registers that
need save before use, to preserve their value across function calls.
The saved registers are part of the stack frame, even if I did not
mention that above to keep things simple. They are restored before
the current function exits.

One nice thing about stack-oriented architectures is that, since
there are no registers, there is never any need to save and
restore them. (Register-oriented hardware seems to have won the
CPU speed wars rather decisively, though. As we all know, the
most important thing for any computer is *how fast* it is, not
whether it gets the right answers. :) )
Another thing to be known is "alloca". This function allocates storage
from the stack by decreasing the stack pointer. A function that calls
alloca must do the popping of the current stack frame in a different
manner since the current stack frame is "deformed" by alloca.

Indeed, an alloca() that adjusts the stack pointer wreaks havoc on
systems that use a hardware-provided stack and have a clever (but
not super-smart) compiler, since the clever compiler assumes that
only *it* ever does this stack-pointer adjustment. This is why
one *cannot* write this kind of alloca() with some compilers on
some machines -- which is, in turn, a reason (perhaps "the" reason)
why alloca() is not in Standard C: the guys writing the C standard
were aware of this problem. Instead, C99 provides "variable length
arrays":
And yet another thing: in standard C, we can have objects whose size
is allocated in local storage but whose exact size is unknown until
run time. This is similar to alloca, and produces the same consequences.

Unlike alloca(), however, VLAs use special syntax. (A call to the
alloca() function looks like, and indeed sometimes is[%], an ordinary
function call.) By using special syntax, VLAs *force* the compiler
to participate in their creation. This participation means that
the compiler "knows" that the (hardware-provided) stack has been
altered at the point the VLA's storage is allocated, and can take
whatever actions are required to make it work. (This can be as
simple as allocating a "variable size stack frame" instead of a
"fixed size stack frame", along with all the consequences that go
with this: on MIPS machines, for instance, one marks the difference
in the debug table that goes with the binary, and uses a hardware
register as a frame pointer instead of using a "virtual" frame
pointer.)

[% I say "sometimes is" because some overly-clever compilers -- gcc
being one example -- will replace what *looks like* a call to
alloca() with appropriate machine code. This even goes as far
as switching from fixed-size to variable-size frames on machines
like the MIPS. In other words, "calling" alloca() does nothing
of the sort, and makes the compiler do the same work as for a
VLA.]

There is a subtle difference between "alloca-allocated storage"
and "VLA-allocated storage", in that the latter obeys block scope
rules:

void f(void) {
size_t size;
char *p;

for (size = 100; size < 10000; size++) {
char space[size];
... more code, possibly setting p = &space ...
}
}

The function f() needs about 10 kbytes of space for its activation
record, since the largest the VLA named "space" will be is 10000.
However:

void g(void) {
size_t size;
char *p;

for (size = 100; size < 10000; size++) {
char *space = alloca(size);
... more code, possibly setting p = &space ...
}

needs about 50 gigabytes (!), because each allocation must persist
for the duration of function g(). After the first iteration of
the loop in f(), the 100-element VLA named "space" is released
before the 101-element VLA named "space" is created (in the second
iteration of the loop); but in g(), the 100-element area allocated
by the first "call" to the alloca() pseduo-function must persist
when the 101-element area is allocated; both of those persist when
the 102-element area is allocated; and so on. (This matters if
you keep pointers to the "previous instance" around, e.g., if p
might refer to a previous loop iteration.)
 
P

polas

In embedded ("non-hosted") environments, it is not unusual for
I/O space to be mapped onto memory space. Mapped I/O may or may
not exhibit storage characteristics.

Thanks to both yourself and Santosh for answering my question - what
you have both said makes sense to me and has helped my understanding.

Nick
 
K

Keith Thompson

jacob navia said:
Yes, compilers are an "artifact" of compiled languages. Obviously
you think that C is not one of them.

or what ???

You misunderstand. A "stack frame" is an artifict of the compiler.
Basic knowledge about compiler code generation is needed if you want
to be able to understand what is going on within your program.

Not really, though it doesn't hurt.

Knowledge of how to use a keyboard or similar input device is needed
to write programs, but we don't give touch-typing lessons here.
Obviously it is not required, as you can drive a car for miles without
needing to know that it needs gas to run.

"According to my car user's manual, if I press this pedal the car
should move. It doesn't say ANYWHERE that gas is needed".

And how does this metaphor apply? What terrible thing happens if you
don't know what a "stack frame" is?

[snip]
 
K

Keith Thompson

jacob navia said:
Obvious, because all libraries in the OS must be called with specific
calling conventions and register usage agreements. All programs must
call the OS to do I/O. They must respect the OS ABI to call those basic
OS routines.

The OS libraries are used by ALL code that runs in that system
eventually, no matter what language the end user is using.

And there's the key phrase: "no matter what language".

Everything you described in the article that started this thread, to
the extent that it's applicable at all, is applicable to most or all
compiled programming languages. It has nothing to do with C. Why do
you insist on discussing it in comp.lang.c?
 
K

Keith Thompson

Chris Torek said:
We can (conceptually) describe the stack frame as follows

struct tagStackFrame {
void *Previous;
void *ThisFrame;
char LocalStorage[];
};

There is no need for a separate "this frame" pointer.

And I'm not convinced there's a need for a Previous frame pointer.

Given a contiguous hardware-managed stack of the kind jacob assumes
(and to which he only barely acknowledges any possible alternatives),
a stack frame could simply keep track of its size. On exit from the
function, the value of the stack pointer is simply adjusted by that
size. Access to the contents of the previous stack frame is not
needed, since C doesn't have nested functions and cannot access local
objects in anything other than the current function. (Other
languages, such as Pascal, can do this, and therefore require somewhat
more elaborate stack layouts; in some environments it might make sense
to use such layouts for all compiled languages.)
In any case, the more general term, which covers what a C compiler
*must* provide in effect, is "activation record". Any given
"activation record" -- whether it is on a stack or not -- contains
the "local storage" for the function, as you describe here.

But the activation record for a function needn't be a single
fixed-size chunk, even ignoring VLAs. Local objects exist at block
scope, not at function scope. Given:

void func(void)
{
int x[1000];
{
int y[2000];
}
{
int z[4000];
}
}

We can assume that allocation for the outermost block is handled on
entry to the function, so the initial activation record must be at
least 1000 bytes -- but the space for y and z needn't be allocated
until those blocks are actually executed. (In a more complex
function, they might never be executed.)

A naive compiler might allocate a single 7000-byte activation record,
with separate storage for x, y, and z. <OT>gcc does this; oh,
well.</OT> A slightly more clever compiler might allocate a 5000-byte
activation record, with storage for y and z overlapped. A more
ambitious compiler might allocate just a 1000-byte activation record,
and allocate space for y and z only when their respective blocks are
entered.

I can't think of any good reason not to perform the simpler
optimization (the 5000-byte activation record). Allocating storage
for each block only when the block is entered (in effect treating each
block as an inline function) imposes some overhead, but could easily
be worthwhile if it saves substantial space. If there's a function
call in the block after the declaration of y, there's no reason to
waste space for storage for z during the execution of the called
function.

[snip]
 
K

Kaz Kylheku

The stack frames are a linked list of this storage areas.

Not, for instance, when you are using gcc with -fomit-frame-pointer.
Then there is no linked list.

Full blown frames are useful for run-time introspection (debugging).
Otherwise, setting them up is nothing but a performance hit, because
it costs cycles and because dedicating a register to be a frame
pointer is a waste of a register.

What stack frame does is allows the local variables to be found
independently of the stack pointer, and it allows a reliable backtrace
to be computed so that the addresses of callers adn their local
variables can be found.

Without a frame pointer, different instruction sequences in a function
use different offsets relative to the stack pointer to find local
variables. Information like how large the stack frame is is implicit
in the generated code. To perform a backtrace, the backtrace routine
must analyze the code to determine how many bytes it will pop off the
stack. To know where the value of a local variable is, the debugger
has to look at the instruction pointer and know what the stack is
doing at that point in the code. This makes the debugger more complex
and less reliable.

Space reserved for local variables and a return address, but without
the debugging support, may still be called a stack frame, even without
the frame pointer and backward linkage. It's just not a ``full blown''
stack frame.
 
K

Keith Thompson

jacob navia said:
Recently K. Thompson asked me to specify what did I understand with
"stack frame". I think that it is important for people using the
language, that this important concept is clear, so I have produced
this post.

I also asked you to cite the standard to support your definition. Of
course the standard doesn't mention stacks, much less stack frames,
which was my real point.

[big snip]
Another thing to be known is "alloca". This function allocates storage
from the stack by decreasing the stack pointer. A function that calls
alloca must do the popping of the current stack frame in a different
manner since the current stack frame is "deformed" by alloca.

By decreasing the stack pointer? So you're not just assuming a
continuous stack, you're assuming that it grows in a particular
direction. What is your basis for this assumption? (Hint: "None" is
the correct answer.)

(Thanks to Chris Torek for posting the response I was going to post,
and probably doing a better job of it.)
 
K

Keith Thompson

jacob navia said:
Basic knowledge about compiler code generation is needed if you want
to be able to understand what is going on within your program.
[...]

What's even more important is an understanding of the difference
between a programming language (as defined by a standard) and a
particular implementation of that language. This is a distinction
that you insist in blurring.
 
J

jacob navia

Kaz said:
Not, for instance, when you are using gcc with -fomit-frame-pointer.
Then there is no linked list.

Yes, there is. The stack pointer is implicitly the frame pointer.

As you explain later, the debug information fills the missing gaps so
that a debugger can walk the stack...

The linked list is conceptually there, no longer explicit but implicit
 
J

jacob navia

Keith said:
jacob navia said:
Basic knowledge about compiler code generation is needed if you want
to be able to understand what is going on within your program.
[...]

What's even more important is an understanding of the difference
between a programming language (as defined by a standard) and a
particular implementation of that language. This is a distinction
that you insist in blurring.

Funny.

Where (yes WHERE) in my message did I mention something
implementation specific?
 
K

Kaz Kylheku

Yes, there is. The stack pointer is implicitly the frame pointer.

As you explain later, the debug information fills the missing gaps so
that a debugger can walk the stack...

The linked list is conceptually there, no longer explicit but implicit

By this definition, a sequence implemented as an array is still
``conceptually'' a linked list.

But it's actually an abstract sequence that's implemented as an array.
The concept is that of sequence.

``Linked list'' is the name given to a sequence data structure in
which we find a successor by chasing a pointer that is embedded in the
predecessor.

If we find the successor by displacing relative to the location of the
predecessor, we are not following a link.
 
J

jacob navia

Kaz said:
By this definition, a sequence implemented as an array is still
``conceptually'' a linked list.

But it's actually an abstract sequence that's implemented as an array.
The concept is that of sequence.

``Linked list'' is the name given to a sequence data structure in
which we find a successor by chasing a pointer that is embedded in the
predecessor.

If we find the successor by displacing relative to the location of the
predecessor, we are not following a link.

Call it as you wish. It is not just like in an array since the offsets
to follow are NOT fixed displacements but displacements of different
sizes. Since we are following those links in the debug information,
in my opinion we are still following a linked chain. But if you think
you want it to be named otherwise (please propose a name) please do so.

What is important is to be clear of what we are talking about.
 
J

J. F. Lemaire

[...]
Everything you described in the article that started this thread, to
the extent that it's applicable at all, is applicable to most or all
compiled programming languages. It has nothing to do with C. Why do
you insist on discussing it in comp.lang.c?

Just a guess: because his signature contains a link to C-related
commercial software? In other words, it's called spam and there are
very effective protections against that.
 
J

jacob navia

J. F. Lemaire said:
[...]
Everything you described in the article that started this thread, to
the extent that it's applicable at all, is applicable to most or all
compiled programming languages. It has nothing to do with C. Why do
you insist on discussing it in comp.lang.c?

Just a guess: because his signature contains a link to C-related
commercial software? In other words, it's called spam and there are
very effective protections against that.

My compiler system can be freely downloaded at no charge.
And this since ten years.

But there people that insist on calling it commercial
since it is sucessful and I do have commercial customers
that interest themselves and pay for my software.

That is even worst for people like Mr lemaire

Where can I download ANY software from Mr lemaire?

What does he give for free to the community?
 
M

Michael Mair

jacob said:
Recently K. Thompson asked me to specify what did I understand
with "stack frame". I think that it is important for people using the
language, that this important concept is clear, so I have produced
this post.

Obviously, there are some people here that live in some
special world where all those things do not exist. They should stay
there and ignore this thread. The same goes for all people that
program in coffee machines and other primitive processors that
do not have a stack pointer or enough registers to use a stack
efficiently.

The above paragraph is contraproductive.
You could instead have asked people to contribute in a constructive
way and point out newsgroups where you also can have this checked.

Chris Torek and Kaz Kylheku already gave you valuable answers, so
I'll just go with the small stuff.

I'll assume that you already told your gentle readers what a stack is.
The notion of "activation record" as explained by Chris should be
mentioned here before you go for the special stack frame.
If you want to stay generic, you can stay with activation records.
The stack frames are a linked list of this storage areas.
^these

I'd rather go for "sequence" (see Kaz's reply) and mention linked
list as "common implementation".
This builds
the conceptual stack that the language needs.

Again, this assumes that you told the reader about "the stack".
Each stack frame is
linked to the previous one. A new stack frame is built when a
procedure is entered, and it is popped from the stack of stack frames
at the procedure exit.

This is not exactly in keeping with what you told above about the
linked list. Mixing stack as data structure and "the stack" will
not necessarily help your readers.
The "main" function has the first (visible) frame, and each stack
frame built in one of the functions called after "main" started is
linked to that stack frame. This makes it possible for debuggers
and other tools to "walk the stack" of called procedures.

We can (conceptually) describe the stack frame as follows

struct tagStackFrame {
void *Previous;
void *ThisFrame;
char LocalStorage[];

For CLC, you may want to have "unsigned char".
};

The "Next" member is a pointer to the previous function's stack frame,
^Previous
the "ThisFrame" pointer points to the current stack frame, and the
Local storage is made out of different variables that a function
declares.

What is for
struct tagStackFrame S;
the relationship between S and S.ThisFrame?
We can imagine that the local storage of deeper nested
blocks is added to the function's local storage, even if it is not
visible (i.e. can't be accessed) after its scope disappears.

In this context, a quick mention of the implications of "goto"
and weak variants thereof at the end may be interesting.
Building the stack frame (Prologue)
------------------------

The first thing to do to build a stack frame is then, to save in the
"Previous" slot, the value of the current stack frame pointer.
Normally, this is a dedicated register. For instance, the power PC
version of lcc-win uses register 31. This register is normally
defined by the operating system ABI (Application Binary Interface)
since ALL programs running in the same operating system MUST agree
as to what the frame pointer register is, if not CHAOS is surely the
consequence.

I think you are too vague here.
I'd call it "common practice" to assuage all voices telling you
of special cases and explain the advantages of all programmes (and
consequently, all implementations) holding to the same convention.
"Unending CHAOS will ensue" is not very convincing, IMO.
After saving the current FP (or Frame Pointer) we copy the current
value of the stack pointer into the frame pointer, to establish a new
stack frame from this stack position on. We fill then, the "ThisFrame"
slot of our structure, and lastly, we subtract from the frame pointer
the amount of space needed by the function's local variables, our
"Local storage" member.

There exist MANY variation of this scheme. Some optimizing compilers
save themselves the trouble of having a different register than the
stack pointer to build a stack frame, and just use the stack pointer to
address local variables. Conceptually however, nothing changes.

Popping the stack frame (Epilogue)

Why? What is the consequence if it does not? (Please not just "chaos")
This is done in the reverse order of the previous actions. The
space allocated for local variables is released by adjusting the stack
pointer, the previous value of the stack frame is popped from the stack,
and the function can now return.

Other stuff
-----------

I'd call it Miscellaneous or Specialities in order to sound sufficiently
important ;-)
A function uses registers to do its calculations. Those register can be
^registers
scratch registers, i.e. registers that can be used without having to
save them, or they can be registers that need save before use, to
preserve their value across function calls. The saved registers are part
of the stack frame, even if I did not mention that above to keep things
simple. They are restored before the current function exits.

Another thing to be known is "alloca". This function allocates storage
from the stack by decreasing the stack pointer. A function that calls
alloca must do the popping of the current stack frame in a different
manner since the current stack frame is "deformed" by alloca.

And yet another thing: in standard C, we can have objects whose size
is allocated in local storage but whose exact size is unknown until
run time. This is similar to alloca, and produces the same consequences.

See Chris' remarks on alloca() vs. VLAs.
As mentioned above, something on goto might be interesting around here.
Maybe setjmp()/longjmp(), too.


Cheers
Michael
 
J

jacob navia

Michael said:
This is not exactly in keeping with what you told above about the
linked list. Mixing stack as data structure and "the stack" will
not necessarily help your readers.

At the procedure prologue, the new stack frame is pushed into
the stack of existing stack frames. At procedure exit time,
(epilogue) the pushed frame is popped from the stack of stack
frames.

That is quite clear.

The "main" function has the first (visible) frame, and each stack
frame built in one of the functions called after "main" started is
linked to that stack frame. This makes it possible for debuggers
and other tools to "walk the stack" of called procedures.

We can (conceptually) describe the stack frame as follows

struct tagStackFrame {
void *Previous;
void *ThisFrame;
char LocalStorage[];

For CLC, you may want to have "unsigned char".

Mmm maybe but why?
What is for
struct tagStackFrame S;
the relationship between S and S.ThisFrame?

The access to the LocalStorage member is done using offsets from the
"This frame" pointer.
In this context, a quick mention of the implications of "goto"
and weak variants thereof at the end may be interesting.


I mentioned some of the problems, but not all of them. Yes, in this
context should come the goto side effects, etc.

I will post several other articles about this stack theme, so I will
expand this later.
 
C

CBFalconer

jacob said:
Yes, compilers are an "artifact" of compiled languages. Obviously
you think that C is not one of them.

or what ???

The point is that this is not suitable for c.l.c.
comp.compilers.lcc would be suitable, as would other suggestions
that have been made. The c language implementation MAY use a
stack, but does not have to. Therefore stacks (except actual code
to implement one) are off-topic on c.l.c. I believe you have had
this explained to you earlier. In fact, several times.
 

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

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top