offsetof() macro

K

Keith Thompson

DevarajA said:
Another thing.. looking into stddef.h I've seen that for C, NULL it is
defined as ((void*)0). The standard specifies that plain 0 is already
a null pointer constant. So why that cast? Maybe this is the last
stupid question for today :)

No, looking into the stddef.h header for your implementation tells you
how your implementation defines NULL. Another implementation could
define it as 0.

The standard says that a null pointer constant is "An integer constant
expression with the value 0, or such an expression cast to type void *",
and that NULL is a macro that "expands to an implementation-defined
null pointer constant".

One advantage of defining NULL as ((void*)0) rather than as 0 is that
it can catch some errors. A constant 0, though it's a null pointer
constant, can also be used in a non-pointer context; ((void*)0)
cannot. For example, I've seen code that incorrectly uses NULL to
denote a null character:
char s[50];
s[0] = NULL;
If NULL is defined as 0, this won't be diagnosed. If it's defined as
((void*)0), it will be.

Please ignore the remainder of this message.

There's actually some doubt about whether ((void*)0) is a legal
definition for NULL. The standard says that a constant 0 cast to
void* is a null pointer constant. It doesn't say that a constant 0
cast to void* *and enclosed in parentheses* is a null pointer
constant. C99 6.5.1 says a parenthesized expression "is an lvalue, a
function designator, or a void expression if the unparenthesized
expression is, respectively, an lvalue, a function designator, or a
void expression"; it doesn't say that a parenthesized expression is a
null pointer constant if the unparenthesized expression is a null
pointer constant.

This is nothing more than a minor glitch in the wording of the
standard, of no real significance either to programmers or to
implementers.
 
F

Flash Gordon

DevarajA wrote:

Another thing.. looking into stddef.h I've seen that for C, NULL it is
defined as ((void*)0). The standard specifies that plain 0 is already a
null pointer constant. So why that cast? Maybe this is the last stupid
question for today :)

The C standard allows the implementation to include the case, and the
reason that some implementations do so is to prevent you from using NULL
in an integer context. So on your implementation the following are all
constraint violations because pointers are not automatically converted
to numbers:

char c = NULL;
int i = NULL;
float f = NULL;
double d = NULL;

However, on an implementation where NULL is defined as plain 0 (which
the standard allows) all of the above are legal even though to a human
reader they don't make sense.

One reason people do mistakenly assign NULL to char variables (or
elements of char arrays) is because the ASCII character set (which C
does not mandate) calls character '\0' NUL and the C standard I believe
calls it a null character, so it is easy to get it confused with NULL.
 
K

Kenneth Brody

DevarajA wrote:
[...]
Another thing.. looking into stddef.h I've seen that for C, NULL it is
defined as ((void*)0). The standard specifies that plain 0 is already a
null pointer constant. So why that cast? Maybe this is the last stupid
question for today :)

Probably to explicitly make it a pointer.

That, and to give warnings when someone does:

char *pt;
...
while ( *pt != NULL )
...

--
+-------------------------+--------------------+-----------------------------+
| Kenneth J. Brody | www.hvcomputer.com | |
| kenbrody/at\spamcop.net | www.fptech.com | #include <std_disclaimer.h> |
+-------------------------+--------------------+-----------------------------+
Don't e-mail me at: <mailto:[email protected]>
 
K

Kenneth Brody

Irrwahn said:
There are no stupid questions, only stupid answers. :)

You've obviously never hung around ASR and ATSR, have you? They'll give
you plently of "stupid question" examples. :)

--
+-------------------------+--------------------+-----------------------------+
| Kenneth J. Brody | www.hvcomputer.com | |
| kenbrody/at\spamcop.net | www.fptech.com | #include <std_disclaimer.h> |
+-------------------------+--------------------+-----------------------------+
Don't e-mail me at: <mailto:[email protected]>
 
W

Walter Roberson

Depends on the implementation, yours happen to use the above
definition, another might define NULL as (0) or even
((void *)('9'-'4'-'5')).

That would only be equivilent to 0 in the rather unusual case
that the representation of the digits started from 0 instead of
(e.g.) ASCII's 48.

Perhaps you meant ((void *)('9'-'4'-5))
 
I

Irrwahn Grausewitz

That would only be equivilent to 0 in the rather unusual case
that the representation of the digits started from 0 instead of
(e.g.) ASCII's 48.

Perhaps you meant ((void *)('9'-'4'-5))

Umm... something like that... brain fart on my side...
me dumba, he he. :)

Thanks for pointing out & best regards
 
T

Tim Rentsch

Keith Thompson said:
DevarajA said:
Keith Thompson ha scritto: [...]
A "null pointer" and a "null pointer constant" are two different
things. A null pointer constant is a construct that can appear in
C source code. A null pointer is a pointer value that can occur
during execution time. An integer constant 0 is a valid null
pointer constant. If you assign a null pointer constant to a
pointer object, that object's value at execution time will be a
null pointer. The execution-time representation of a null pointer
may or may not be all-bits-zero. (If a null pointer has a
representation other than all-bits-zero, it's up to the compiler to
do whatever conversion might be necessary.) The macro NULL expands
to a null pointer constant. You cannot legally redefine NULL
yourself. Your compiler might let you get away with it, but
there's absolutely no reason to do so.

Then using NULL is the same as using 0? So why in C many people use
NULL? Is that to avoid portability problems (in the case of a weird
non-standard implementation that has the null ptr const !=0)?

The advantage of using NULL is documentation. It makes it obvious
that what's intended is a pointer value, not an integer value.

For example:

a = 0;
b = 0.0;
c = '\0';
d = NULL;

You can tell even without looking at the declarations (assuming a sane
programmer) that a is an integer, b is a floating-point variable, c is
a character, and d is a pointer.

A complaint I have about NULL is that NULL isn't guaranteed to be
a pointer. Supplying NULL as a trailing argument (corresponding
to a post-ellipsis parameter), for example, isn't guaranteed to
pass a pointer rather than an int 0. It would be nice if the
Standard were changed to fix this.


[snip]
The current confusing situation is, I think, a remnant of the early
days of C when it was assumed that a null pointer *was* all-bits-zero,
and even non-zero integer constants were commonly used as pointers.

Perhaps partly, but there's another factor that may play a larger
role here. C deliberately proclaimed null pointers as zero and
non-null pointers as non-zero, so that they would work nicely
with if's and while's. People who learned C later seem to like
explicit comparisons (whether to NULL or 0); "old timers" are
more used to seeing things like

if(p) ...
if(!p) ...

and may even prefer them. For these forms, it's convenient to
think of null pointers as being all-bits-zero, even if in the
back of the mind it's known not to always be true.
 
T

Tim Rentsch

Keith Thompson said:
There's actually some doubt about whether ((void*)0) is a legal
definition for NULL. The standard says that a constant 0 cast to
void* is a null pointer constant. It doesn't say that a constant 0
cast to void* *and enclosed in parentheses* is a null pointer
constant. C99 6.5.1 says a parenthesized expression "is an lvalue, a
function designator, or a void expression if the unparenthesized
expression is, respectively, an lvalue, a function designator, or a
void expression"; it doesn't say that a parenthesized expression is a
null pointer constant if the unparenthesized expression is a null
pointer constant.

Whatever NULL is defined as seems like it must be a null pointer
constant, by definition. Per 7.17 p3, NULL "expands to an
implementation-defined null pointer constant". So even if we had

#define NULL the rain in Spain falls mainly on the plain

it would be (defined by the implementation as) a null pointer
constant. It's just up to the other parts of the implementation
to insure that it behaves as one.

Is it true that, except for function pointers, it's unnecessary
to have '(void*) <constant-zero-expression>' be defined as a
null pointer constant?
 
I

Irrwahn Grausewitz

Tim Rentsch said:
Whatever NULL is defined as seems like it must be a null pointer
constant, by definition. Per 7.17 p3, NULL "expands to an
implementation-defined null pointer constant". So even if we had

#define NULL the rain in Spain falls mainly on the plain

it would be (defined by the implementation as) a null pointer
constant. It's just up to the other parts of the implementation
to insure that it behaves as one.

IOW: the implementation has to take care that
the rain in Spain falls mainly on the plain
is treated as an integer constant expression with the value 0,
possibly cast to void * (see below). Ugly, but IMHO feasible.
But ugly. Did I already mention ugly?
Is it true that, except for function pointers, it's unnecessary
to have '(void*) <constant-zero-expression>' be defined as a
null pointer constant?

In which aspect are function pointers different? Consider:

ISO/IEC 9899:1999 (E)
6.3.2.3p3
An integer constant expression with the value 0, or such an
expression cast to type void *, is called a null pointer constant.
If a null pointer constant is converted to a pointer type, the
resulting pointer, called a null pointer, is guaranteed to compare
unequal to a pointer to any object or function.

6.5.16.1
1 One of the following shall hold:
[...]
— the left operand is a pointer and the right is a null pointer
constant
[...]
2 In simple assignment (=), the value of the right operand is
converted to the type of the assignment expression and replaces the
value stored in the object designated by the left operand.

Did I miss anything important?

Best Regards
 
K

Keith Thompson

Tim Rentsch said:
A complaint I have about NULL is that NULL isn't guaranteed to be
a pointer. Supplying NULL as a trailing argument (corresponding
to a post-ellipsis parameter), for example, isn't guaranteed to
pass a pointer rather than an int 0. It would be nice if the
Standard were changed to fix this.

Changed how, exactly? Even if you required NULL to be defined as,
say, ((void*)0), it would evaluate to a null pointer of type void*,
which could still cause problems if you need a null pointer of some
other pointer type as an argument to a function with a variable number
of arguments. Yes, ((void*)0) does catch some errors that 0 doesn't,
but you still need to use (foo*)NULL rather than just NULL in some
cases.

Now if I were designing the language from scratch, "nil" would be a
keyword that would evaluate to a null pointer of whatever type is
implied by the context. If the context didn't imply a particular
pointer type, a cast would be required.
 
R

Richard Bos

That would only be equivilent to 0 in the rather unusual case
that the representation of the digits started from 0 instead of
(e.g.) ASCII's 48.

Which is actually not allowed, since that would mean '0' would be the
string-terminating null character.

Richard
 
T

Tim Rentsch

Keith Thompson said:
Changed how, exactly? Even if you required NULL to be defined as,
say, ((void*)0), it would evaluate to a null pointer of type void*,
which could still cause problems if you need a null pointer of some
other pointer type as an argument to a function with a variable number
of arguments. Yes, ((void*)0) does catch some errors that 0 doesn't,
but you still need to use (foo*)NULL rather than just NULL in some
cases.

For example, adding a statement that says NULL will always
evaluate to some pointer type would be an improvement. I agree
that there are cases where casts would need to be added, such as
the one you mention. But casts are going to be needed in some
cases in any event. Requiring NULL to evaluate to some pointer
type would lessen the number of such cases, and as a practical
matter would tend to produce better behavior in cases where casts
were needed but left out.

On the flip side, I don't see any benefit in allowing NULL to
evaluate to an integer type rather than a pointer type, as the
current language does.

Now if I were designing the language from scratch, "nil" would be a
keyword that would evaluate to a null pointer of whatever type is
implied by the context. If the context didn't imply a particular
pointer type, a cast would be required.

In effect that's what I'm suggesting, with these changes: one,
the language element be a macro rather than a keyword; two, the
language element be spelled "NULL" rather than "nil"; three, if
context doesn't imply a particular pointer type, 'void *' is the
type that results. I don't mean to claim that one approach is
better than the other, only that the two suggestions are not that
different from each other.
 
T

Tim Rentsch

Irrwahn Grausewitz said:
IOW: the implementation has to take care that
the rain in Spain falls mainly on the plain
is treated as an integer constant expression with the value 0,
possibly cast to void * (see below).

That's a way of reading it; however, it isn't what I meant to
suggest. Rather, we can read 7.17 p3 as saying that whatever
NULL is defined as is a null pointer constant, regardless of
whether it involves the integer value 0 (constant or otherwise),
and regardless of whether it involves casting to 'void *'. In
other words, the exansion for NULL is a null pointer constant
because 7.17 p3 stipulates that it is. The expressions '0' and
'(void*) 0' are null pointer constants, because 6.3.2.3 says they
are; however, an implementation is free to define other
expressions as null pointer constants -- such as for example

(void*)(char*)(void*)0

and whatever NULL expands to is one such implementation-defined
null pointer constant.


Is it true that, except for function pointers, it's unnecessary
to have '(void*) <constant-zero-expression>' be defined as a
null pointer constant?

In which aspect are function pointers different? Consider:

ISO/IEC 9899:1999 (E)
6.3.2.3p3
An integer constant expression with the value 0, or such an
expression cast to type void *, is called a null pointer constant.
If a null pointer constant is converted to a pointer type, the
resulting pointer, called a null pointer, is guaranteed to compare
unequal to a pointer to any object or function.

6.5.16.1
1 One of the following shall hold:
[...]
— the left operand is a pointer and the right is a null pointer
constant
[...]
2 In simple assignment (=), the value of the right operand is
converted to the type of the assignment expression and replaces the
value stored in the object designated by the left operand.

Did I miss anything important?

Consider:

int (*function_pointer_1)( int ) = (void*) 0;
int (*function_pointer_2)( int ) = (char*) 0;

The first of these declarations is ok, the other is a type error.
The first declaration works only because '(void*) 0' is a null
pointer constant, and it's allowed to assign a null pointer
constant to a function pointer. Assigning an expression of type
'void *', but not a null pointer constant, to a function pointer
is not allowed.

So if '(void*) 0' were not a null pointer constant, then the
first declaration wouldn't work (because the initializing
expression exhibits a type mismatch).
 
K

Keith Thompson

Tim Rentsch said:
For example, adding a statement that says NULL will always
evaluate to some pointer type would be an improvement. I agree
that there are cases where casts would need to be added, such as
the one you mention. But casts are going to be needed in some
cases in any event. Requiring NULL to evaluate to some pointer
type would lessen the number of such cases, and as a practical
matter would tend to produce better behavior in cases where casts
were needed but left out.

On the flip side, I don't see any benefit in allowing NULL to
evaluate to an integer type rather than a pointer type, as the
current language does.

Ok. A simpler way to express this would be to require NULL to be
defined as ((void*)0) (and to tweak the wording of the definition of
"null pointer constant" to make it clear that that's legal). I would
have no problem with that; I don't see the benefit of allowing each
implementation to decide which null pointer constant it will use in
the definition of NULL.
In effect that's what I'm suggesting, with these changes: one,
the language element be a macro rather than a keyword; two, the
language element be spelled "NULL" rather than "nil"; three, if
context doesn't imply a particular pointer type, 'void *' is the
type that results. I don't mean to claim that one approach is
better than the other, only that the two suggestions are not that
different from each other.

My idea (which is totally impractical because it would break existing
code) is that the "nil" keyword would be the *only* legal null pointer
constant.
 
K

Keith Thompson

Tim Rentsch said:
That's a way of reading it; however, it isn't what I meant to
suggest. Rather, we can read 7.17 p3 as saying that whatever
NULL is defined as is a null pointer constant, regardless of
whether it involves the integer value 0 (constant or otherwise),
and regardless of whether it involves casting to 'void *'. In
other words, the exansion for NULL is a null pointer constant
because 7.17 p3 stipulates that it is. The expressions '0' and
'(void*) 0' are null pointer constants, because 6.3.2.3 says they
are; however, an implementation is free to define other
expressions as null pointer constants -- such as for example

(void*)(char*)(void*)0

and whatever NULL expands to is one such implementation-defined
null pointer constant.

C99 6.3.2.3p3 defines the term "null pointer constant":

An integer constant expression with the value 0, or such an
expression cast to type void *, is called a _null pointer
constant_.

I don't see any permission for the implementation to define null
pointer constants that don't meet that definition. 7.17p3 says that
the macro NULL "expands to an implementation-defined null pointer
constant". This allows the implementation to define NULL as either 0,
(void*)0, or ('/'/'/'-'/'/'/'); it doesn't allow it to define NULL as
(void*)(char*)0. 7.17p3 constrains the implementation's definition of
NULL; it doesn't define "null pointer constant".
 
T

Tim Rentsch

Keith Thompson said:
My idea (which is totally impractical because it would break existing
code) is that the "nil" keyword would be the *only* legal null pointer
constant.

Ahh I see. The revised language would not only add 'nil' but
also would take away '0' (and '(void*)0') as a way of writing
null pointer values. Clearly there are some benefits to using
'nil' as the only form of null pointer constant. What do you
think the costs are, not counting the cost of converting existing
code so that it uses 'nil'? Or have you thought much about what
the costs would be?
 
T

Tim Rentsch

Keith Thompson said:
C99 6.3.2.3p3 defines the term "null pointer constant":

An integer constant expression with the value 0, or such an
expression cast to type void *, is called a _null pointer
constant_.

I don't see any permission for the implementation to define null
pointer constants that don't meet that definition. 7.17p3 says that
the macro NULL "expands to an implementation-defined null pointer
constant". This allows the implementation to define NULL as either 0,
(void*)0, or ('/'/'/'-'/'/'/'); it doesn't allow it to define NULL as
(void*)(char*)0. 7.17p3 constrains the implementation's definition of
NULL; it doesn't define "null pointer constant".

I agree the issue is not clear cut. I wasn't meaning to start a
debate on the issue, only to give a plausible and reasonable
alternative reading. However, since you ask, let me frame some
arguments.

1. The Standard isn't always careful in how definitions are given.
There was discussion recently on the definition of "definition", for
example. Although I think it would be a good idea if the Standard
were more rigorous (and, dare I say it, "mathematical") in defining
and using special terms, that isn't what it does now.

2. Note the particular wording in 6.3.2.3 p3: such-and-such kind of
expression *is called* a null pointer constant. It doesn't say the
list is exhaustive. If there are any candidates for definitions in
the Standard that are one-way definitions rather than two-way
definitions, surely the statement in 6.3.2.3 p3 must be one of them.

3. The mere use of the term "implementation-defined null pointer
constants" suggests that there are some null pointer constants that
are defined by the implementation rather than by the Standard.
Otherwise, 7.17 p3 could have said just that "NULL expands to a null
pointer constant". Based on the way other parts of the Standard are
written, the more concise form seems more likely if an implementation
were not allowed to define null pointer constants (perhaps with a
footnote that says, eg, "The choice of which null pointer constant
expression to use is implementation defined.").

4. If we take 6.3.2.3 p3 as precisely delimiting the set of
expressions that are null pointer constants, then, as you yourself
pointed out, '((void*)0)' would not be a null pointer constant.
Yet

int (*fp)(int);
fp = ((void*)0);

works. (At least, I'm not aware of any implementation where it
doesn't work.) This code isn't allowed to work (without a diagnostic)
unless '((void*)0)' were a null pointer constant. So what are we to
believe? That lots of implementations got it wrong, or that the set
of expressions mentioned in 6.3.2.3 p3 is not exhaustive? The second
alternative seems more likely. If the set of expressions mentioned in
6.3.2.3 p3 is not exhaustive, then of course any additional forms of
null pointer constant would be implementation defined.

5. In the absence of any clearly compelling argument to the contrary,
the reading that puts less strain on the interpretation seems better.
Basically, Occam's Razor. Unless there is specific evidence that an
implementation is forbidden from defining particular forms of null
pointer constants, it's a better match to the observed facts to
assume that it's allowed.
 
K

Keith Thompson

Tim Rentsch said:
Ahh I see. The revised language would not only add 'nil' but
also would take away '0' (and '(void*)0') as a way of writing
null pointer values. Clearly there are some benefits to using
'nil' as the only form of null pointer constant. What do you
think the costs are, not counting the cost of converting existing
code so that it uses 'nil'? Or have you thought much about what
the costs would be?

I've used other languages that have a keyword that's used as the
equivalent of a null pointer constant, and that don't allow any other
form of null pointer constant. I'll just mention that programmers in
those languages typically don't know or care how null pointers are
represented; even if they happen to know how a given implementation
represents null pointers, they don't try to make use of the knowledge.
And their FAQs don't have to devote an entire section to null
pointers.

I don't think there are any costs to this approach, apart from the
insurmountable cost of all the existing C code that would be broken.

I'm almost tempted to suggest adding a "nil" keyword to the next C
standard, with the obvious semantics, *without* eliminating the other
forms of null pointer constant. But providing a better approach
without removing the old one would just add to the confusion.

(One way to do this might be to add a declaration
enum { nil };
to <stddef.h>, and to have the compiler issue a warning if it's used
in a non-pointer context.)
 
K

Keith Thompson

Tim Rentsch said:
I agree the issue is not clear cut. I wasn't meaning to start a
debate on the issue, only to give a plausible and reasonable
alternative reading. However, since you ask, let me frame some
arguments.

Actually, we aren't in agreement. I think the issue is (reasonably)
clear-cut.
1. The Standard isn't always careful in how definitions are given.
There was discussion recently on the definition of "definition", for
example. Although I think it would be a good idea if the Standard
were more rigorous (and, dare I say it, "mathematical") in defining
and using special terms, that isn't what it does now.

Agreed. However, there are cases where the standard gives explicit
permission to do something in an implementation-defined way, such as
the declaration of main(). The fact that it doesn't do so here is,
IMHO, significant.
2. Note the particular wording in 6.3.2.3 p3: such-and-such kind of
expression *is called* a null pointer constant. It doesn't say the
list is exhaustive. If there are any candidates for definitions in
the Standard that are one-way definitions rather than two-way
definitions, surely the statement in 6.3.2.3 p3 must be one of them.

Since there's a clear and unambiguous interpretation assuming the
definition is exhaustive, I believe that's the best interpretation.

The standard doesn't give explicit permission for other forms of null
pointer constant. Using (void*)42 in a context that requires a null
pointer constant is a constraint violation. An implementation that
doesn't issue a diagnostic for such a use is non-conforming.

Similarly, the standard doesn't give permission for other forms of
integer constants. An implementation can accept 0b11001001 as a
binary constant as a language extension, but it must issue a
diagnostic for any such constant in conforming mode.
3. The mere use of the term "implementation-defined null pointer
constants" suggests that there are some null pointer constants that
are defined by the implementation rather than by the Standard.
Otherwise, 7.17 p3 could have said just that "NULL expands to a null
pointer constant". Based on the way other parts of the Standard are
written, the more concise form seems more likely if an implementation
were not allowed to define null pointer constants (perhaps with a
footnote that says, eg, "The choice of which null pointer constant
expression to use is implementation defined.").

The term "implementation-defined" doesn't just mean that the
implementation gets to make a choice; it also means it has to document
that choice. If the standard merely said that NULL expands to a null
pointer constant, an implementation wouldn't be required to document
what it actually expands to. This would be no great loss, since code
shouldn't depend on the choice, but I believe the intent is that (a)
an implementation can choose any valid null pointer constant as the
expansion of NULL, and (b) it must document what choice it makes.

It wouldn't make sense to use the phrase "implementation defined" only
in a footnote.
4. If we take 6.3.2.3 p3 as precisely delimiting the set of
expressions that are null pointer constants, then, as you yourself
pointed out, '((void*)0)' would not be a null pointer constant.
Yet

int (*fp)(int);
fp = ((void*)0);

works. (At least, I'm not aware of any implementation where it
doesn't work.) This code isn't allowed to work (without a diagnostic)
unless '((void*)0)' were a null pointer constant. So what are we to
believe? That lots of implementations got it wrong, or that the set
of expressions mentioned in 6.3.2.3 p3 is not exhaustive? The second
alternative seems more likely. If the set of expressions mentioned in
6.3.2.3 p3 is not exhaustive, then of course any additional forms of
null pointer constant would be implementation defined.

I believe the glitch in 6.3.2.3p3, which implies that (void*)0 is a
null pointer constant but ((void*)0) isn't, is simply an oversight. I
suspect the authors of the standard just assumed that an expression in
parentheses is equivalent to the expression without parentheses -- and
of course enclosing macro definitions in parentheses is almost always
a good idea. Implementations actually conform to the intent of the
standard in this case, rather than to the literal wording. I wouldn't
infer anything significant from this (though I'd certainly like to see
this corrected.)
5. In the absence of any clearly compelling argument to the contrary,
the reading that puts less strain on the interpretation seems better.
Basically, Occam's Razor. Unless there is specific evidence that an
implementation is forbidden from defining particular forms of null
pointer constants, it's a better match to the observed facts to
assume that it's allowed.

I disagree. I find it much simpler to assume that the definition of
"null pointer constant" means exactly what it says, modulo the
parentheses glitch. The intent of the phrase "implementation-defined
null pointer constant" in 7.17p3 is quite clear given this
interpretation; there's no need to invent the idea of additional
implementation-defined *forms* of null pointer constant. There's also
no advantage in doing so; we have more than enough null pointer
constants already without allowing implementations to invent their
own.
 

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,794
Messages
2,569,641
Members
45,353
Latest member
RogerDoger

Latest Threads

Top