offsetof() Macro Attempt

S

Shao Miller

Good day, folks. I'm pondering 'offsetof()'. 6.6 "Constant
expressions" might be relevant.

Suppose we've:

int helper[2];

enum foo {
bar = helper + 1 - helper
};

int main(void) {
return 0;
}

Does that code include any undefined behaviour?

How about in:

int (* helper)[2];

enum foo {
bar = *helper + 1 - *helper
};

int main(void) {
return 0;
}

Is there undefined behaviour there? Note that '*helper' is an array
which "decays" to a pointer to the first element. Also note that
'helper' is a pointer to 'int[2]'. But does it matter what the stored
value of 'helper' is or is the value of 'bar' independent of it?

So let's please go on to:

char ** helper;

enum foo {
bar = *helper + 1 - *helper
};

int main(void) {
return 0;
}

Is there undefined behaviour there? So what about in:

char ** helper;

struct s {
int arr[2];
};

enum foo {
bar = ((struct s *)*helper)->arr + 1 - ((struct s *)*helper)->arr
};

int main(void) {
return 0;
}

? And then:

char ** helper;

struct s {
int x;
short y;
int z;
};

enum foo {
bar = (char *)&((struct s *)*helper)->z -
(char *)((struct s *)*helper)
};

int main(void) {
return 0;
}

In the example just above, does the value of 'bar' depend on the value
of 'helper'? Is 'helper' evaluated? If so, what if we use that mess as
part of a type-name in an operand to 'sizeof', as in:

char ** helper;

struct s {
int x;
short y;
int z;
};

enum foo {
bar = sizeof (char[
(char *)&((struct s *)*helper)->z -
(char *)((struct s *)*helper)
])
};

int main(void) {
return 0;
}

Could 'offsetof' be reasonably portable as:

char ** helper;

#define DUMMY_OF(type) ((type *)*helper)
#define ADDRESS_AT(address) ((char *)(address))
#define DUMMY_MEMBER_ADDRESS(type, member) (&DUMMY_OF(type)->member)
#define PROTECT(expression) (sizeof (char[(expression)]))
#define OFFSETOF(type, member) (PROTECT( \
ADDRESS_AT(DUMMY_MEMBER_ADDRESS(type, member)) - \
ADDRESS_AT(DUMMY_OF(type)) \
))

struct s {
int x;
short y;
int z;
};

enum foo {
bar = OFFSETOF(struct s, z)
};

int main(void) {
return 0;
}

Are there any null pointers or other dragons in there?

Thanks for any insight you can provide!
 
B

Billy Mays

...
#define DUMMY_OF(type) ((type *)*helper)
#define ADDRESS_AT(address) ((char *)(address))
#define DUMMY_MEMBER_ADDRESS(type, member) (&DUMMY_OF(type)->member)
#define PROTECT(expression) (sizeof (char[(expression)]))
#define OFFSETOF(type, member) (PROTECT( \
ADDRESS_AT(DUMMY_MEMBER_ADDRESS(type, member)) - \
ADDRESS_AT(DUMMY_OF(type)) \
))

struct s {
int x;
short y;
int z;
};

enum foo {
bar = OFFSETOF(struct s, z)
};

int main(void) {
return 0;
}

Are there any null pointers or other dragons in there?

Thanks for any insight you can provide!


As far as I can tell, it deterministically returns 0 every time.
 
S

Shao Miller

As far as I can tell, it deterministically returns 0 every time.

LOL. Thanks. In your response, the code has lost its indentation. Do
you see indentation in the original code sections?
 
H

Harald van Dijk

Good day, folks.  I'm pondering 'offsetof()'.  6.6 "Constant
expressions" might be relevant.

Suppose we've:

   int helper[2];

   enum foo {
       bar = helper + 1 - helper
     };

   int main(void) {
       return 0;
     }

Does that code include any undefined behaviour?

Yes.

6.6p6:
An integer constant expression shall have integer type and shall only
have operands
that are integer constants, enumeration constants, character
constants, sizeof
expressions whose results are integer constants, and floating
constants that are the
immediate operands of casts. Cast operators in an integer constant
expression shall only
convert arithmetic types to integer types, except as part of an
operand to the sizeof
operator.

Address constants are not permitted in integer constant expressions.
This is not a constraint, so a compiler is free to accept it without
any notice, but the behaviour is undefined.
 
A

Andrey Tarasevich

Good day, folks. I'm pondering 'offsetof()'. 6.6 "Constant
expressions" might be relevant.

Suppose we've:

int helper[2];

enum foo {
bar = helper + 1 - helper
};

int main(void) {
return 0;
}

Does that code include any undefined behaviour?

Undefined behaviour? This code is non-compilable, since enum value
specifiers are required to be integer constant expressions (ICE) and
your expression is not an ICE. ICE is not allowed to contain any address
arithmetic, even if the addresses are constant.

If your compiler somehow lets it through, it must be an extension, so
from the standard language point of view the behavior is
How about in:

int (* helper)[2];

enum foo {
bar = *helper + 1 - *helper
};

int main(void) {
return 0;
}

Also non-compilable. This is even more hopeless, since the address
values are not even constants.
So let's please go on to:

char ** helper;

enum foo {
bar = *helper + 1 - *helper
};

int main(void) {
return 0;
}

Also non-compilable for the very same reasons. The same applies to all
remaining examples.
char ** helper;

struct s {
int x;
short y;
int z;
};

enum foo {
bar = sizeof (char[
(char *)&((struct s *)*helper)->z -
(char *)((struct s *)*helper)
])
};

int main(void) {
return 0;
}

No hope here either. Array size in this context is also required to be
an ICE.
 
E

Eric Sosman

Good day, folks. I'm pondering 'offsetof()'. 6.6 "Constant expressions"
might be relevant.

Suppose we've:

int helper[2];

enum foo {
bar = helper + 1 - helper
};

int main(void) {
return 0;
}

Does that code include any undefined behaviour?

No. Or, rather, "maybe." After emitting the required diagnostic
(for violation of the "shall" in 6.7.2.2p2), the compiler may reject
the program or may accept it and give it an implementation-defined
meaning. That implementation-defined meaning may leave some things
undefined, and if you run into them you'll have undefined behavior.

But you'll have the diagnostic first.
How about in:

int (* helper)[2];

enum foo {
bar = *helper + 1 - *helper
};

int main(void) {
return 0;
}

Is there undefined behaviour there? [...]
Ditto.

So let's please go on to:

char ** helper;

enum foo {
bar = *helper + 1 - *helper
};

int main(void) {
return 0;
}

Is there undefined behaviour there? [...]
Ditto.

char ** helper;

struct s {
int arr[2];
};

enum foo {
bar = ((struct s *)*helper)->arr + 1 - ((struct s *)*helper)->arr
};

int main(void) {
return 0;
}
Ditto.

char ** helper;

struct s {
int x;
short y;
int z;
};

enum foo {
bar = (char *)&((struct s *)*helper)->z -
(char *)((struct s *)*helper)
};

int main(void) {
return 0;
}
Ditto.

In the example just above, does the value of 'bar' depend on the value
of 'helper'? Is 'helper' evaluated? If so, what if we use that mess as
part of a type-name in an operand to 'sizeof', as in:

char ** helper;

struct s {
int x;
short y;
int z;
};

enum foo {
bar = sizeof (char[
(char *)&((struct s *)*helper)->z -
(char *)((struct s *)*helper)
])
};

int main(void) {
return 0;
}
Ditto.

Could 'offsetof' be reasonably portable as:

char ** helper;

#define DUMMY_OF(type) ((type *)*helper)
#define ADDRESS_AT(address) ((char *)(address))
#define DUMMY_MEMBER_ADDRESS(type, member) (&DUMMY_OF(type)->member)
#define PROTECT(expression) (sizeof (char[(expression)]))
#define OFFSETOF(type, member) (PROTECT( \
ADDRESS_AT(DUMMY_MEMBER_ADDRESS(type, member)) - \
ADDRESS_AT(DUMMY_OF(type)) \
))

struct s {
int x;
short y;
int z;
};

enum foo {
bar = OFFSETOF(struct s, z)
};

int main(void) {
return 0;
}
No.

Are there any null pointers or other dragons in there?

No dragons, just diagnostics.
 
E

Eric Sosman

Good day, folks. I'm pondering 'offsetof()'. 6.6 "Constant expressions"
might be relevant.

Suppose we've:

int helper[2];

enum foo {
bar = helper + 1 - helper
};

int main(void) {
return 0;
}

Does that code include any undefined behaviour?

No. Or, rather, "maybe." After emitting the required diagnostic
(for violation of the "shall" in 6.7.2.2p2), [...]

Now having read Harald van Dijk's response, I'm less sure of
this. Failing to provide an integer constant expression violates
the constraint in 6.7.2.2p2, but the relevant part of the I.C.E.
definition is not itself a constraint. So there's a violation of
a constraint, but it rests on a violation of a non-constraint --
and the situation is unclear. So maybe I was fortunate to have
written "maybe." ;-)
 
T

Tim Rentsch

Shao Miller said:
Good day, folks. I'm pondering 'offsetof()'. 6.6 "Constant
expressions" might be relevant.

Suppose we've:

int helper[2];

enum foo {
bar = helper + 1 - helper
};

int main(void) {
return 0;
}

Does that code include any undefined behaviour?

Because of the address constants in the defining
expression for 'bar', this code is conforming
but it is not strictly conforming.
 
T

Tim Rentsch

Harald van Dijk said:
Good day, folks. I'm pondering 'offsetof()'. 6.6 "Constant
expressions" might be relevant.

Suppose we've:

int helper[2];

enum foo {
bar = helper + 1 - helper
};

int main(void) {
return 0;
}

Does that code include any undefined behaviour?

Yes.

6.6p6:
An integer constant expression shall have integer type and shall only
have operands
that are integer constants, enumeration constants, character
constants, sizeof
expressions whose results are integer constants, and floating
constants that are the
immediate operands of casts. Cast operators in an integer constant
expression shall only
convert arithmetic types to integer types, except as part of an
operand to the sizeof
operator.

Address constants are not permitted in integer constant expressions.

Not exactly. Implementations are allowed to accept address
constants in integer constant expressions under 6.6p10. In
such an implementation the behavior is defined, not undefined.
The code is conforming, just not strictly conforming.
This is not a constraint, so a compiler is free to accept it without
any notice, but the behaviour is undefined.

If the implementation does not accept address constants in
integer constant expressions, then the definition for 'bar'
fails to be an integer constant expression, which is a
constraint violation and a diagnostic must be issued.
 
T

Tim Rentsch

Andrey Tarasevich said:
Good day, folks. I'm pondering 'offsetof()'. 6.6 "Constant
expressions" might be relevant.

Suppose we've:

int helper[2];

enum foo {
bar = helper + 1 - helper
};

int main(void) {
return 0;
}

Does that code include any undefined behaviour?

Undefined behaviour? This code is non-compilable, since enum value
specifiers are required to be integer constant expressions (ICE) and
your expression is not an ICE. ICE is not allowed to contain any
address arithmetic, even if the addresses are constant.

Actually they can under 6.6p10.
If your compiler somehow lets it through, it must be an extension, so
from the standard language point of view the behavior is [undefined?]

They aren't extensions in the normal sense in that the
Standard specifically allows them; my best understanding
is that the Standard doesn't require them to be documented,
for example. In any case, the Standard explicitly provides
a way for such expressions to be accepted (without a diagnostic)
by a conforming implementation.
 
T

Tim Rentsch

Eric Sosman said:
Good day, folks. I'm pondering 'offsetof()'. 6.6 "Constant expressions"
might be relevant.

Suppose we've:

int helper[2];

enum foo {
bar = helper + 1 - helper
};

int main(void) {
return 0;
}

Does that code include any undefined behaviour?

No. Or, rather, "maybe." After emitting the required diagnostic
(for violation of the "shall" in 6.7.2.2p2), [...]

Now having read Harald van Dijk's response, I'm less sure of
this. Failing to provide an integer constant expression violates
the constraint in 6.7.2.2p2, but the relevant part of the I.C.E.
definition is not itself a constraint. So there's a violation of
a constraint, but it rests on a violation of a non-constraint --
and the situation is unclear.

The 'shall' in 6.6p6 is part of the definition of integer
constant expression, so if the condition isn't met the
definition isn't satisfied. Hence a diagnostic would be
required, assuming that the expression in question doesn't
qualify as an integer constant expression under 6.6p10
(which in fact it does in some implementations).
 
S

Shao Miller

That one has the same problem as

int main(void)
{
int x;

x - x;
return 0;
}

In your example, 'x' is not initialized, and I agree that such is a
concern. In the example just above your example, 'helper' is at file
scope[6.9.2p2]. What is the problem, exactly?
 
H

Harald van Dijk

Harald van Dijk said:
   int helper[2];
   enum foo {
       bar = helper + 1 - helper
     };
Does that code include any undefined behaviour?
[snip]
Address constants are not permitted in integer constant expressions.

Not exactly.  Implementations are allowed to accept address
constants in integer constant expressions under 6.6p10.  In
such an implementation the behavior is defined, not undefined.
The code is conforming, just not strictly conforming.
This is not a constraint, so a compiler is free to accept it without
any notice, but the behaviour is undefined.

If the implementation does not accept address constants in
integer constant expressions, then the definition for 'bar'
fails to be an integer constant expression, which is a
constraint violation and a diagnostic must be issued.

You're right, I missed the italics in 6.6p6: it defines the term
"integer constant expression", meaning any violation of a "shall"
means the expression is not an integer constant expression, and that
"integer constant expression" does not simply mean "constant
expression of integer type". However, while 6.6p10 grants permission
to accept other forms of constant expressions, I do not see it
altering the definition of 6.6p6. An implementation that accepts
"helper + 1 - helper" as a constant expression under 6.6p10 cannot
accept it as an integer constant expression. It is a constant
expression of integer type, which is not the same thing when 6.6p6
defines i.c.e. differently. Which means that, on such an
implementation, it is acceptable as an initialiser at file scope, but
not as an enumeration constant.

I don't think any implementation behaves this way, so either I'm
missing something, or the standard is.
 
T

Tim Rentsch

Harald van Dijk said:
Harald van Dijk said:
int helper[2];
enum foo {
bar = helper + 1 - helper
};
Does that code include any undefined behaviour?
[snip]
Address constants are not permitted in integer constant expressions.

Not exactly. Implementations are allowed to accept address
constants in integer constant expressions under 6.6p10. In
such an implementation the behavior is defined, not undefined.
The code is conforming, just not strictly conforming.
This is not a constraint, so a compiler is free to accept it without
any notice, but the behaviour is undefined.

If the implementation does not accept address constants in
integer constant expressions, then the definition for 'bar'
fails to be an integer constant expression, which is a
constraint violation and a diagnostic must be issued.

You're right, I missed the italics in 6.6p6: it defines the term
"integer constant expression", meaning any violation of a "shall"
means the expression is not an integer constant expression, and that
"integer constant expression" does not simply mean "constant
expression of integer type". However, while 6.6p10 grants permission
to accept other forms of constant expressions, I do not see it
altering the definition of 6.6p6.

6.6p10 allows 'other forms of constant expressions' plural, which
also includes the different subcategories of constant expression.
One reason for doing this is so implementations can define things
like the 'offsetof' macro using an ordinary expression and still
have that expression be an integer constant expression.

Also, if you look at the description of 'constant expression' (as
opposed to the subcategories) all the restrictions on it are
either syntactic or constraints. Violating either of those would
necessitate a diagnostic message, in which case 6.6p10 would
serve no purpose, since the same result could be achieved by just
defining an extension.
An implementation that accepts
"helper + 1 - helper" as a constant expression under 6.6p10 cannot
accept it as an integer constant expression. It is a constant
expression of integer type, which is not the same thing when 6.6p6
defines i.c.e. differently. Which means that, on such an
implementation, it is acceptable as an initialiser at file scope, but
not as an enumeration constant.

If 6.6p10 applied only to constant expressions and not to integer
constant expressions what you say would be right, but 6.6p10 also
is meant to allow (and does allow, as I read it) implementations
to accept other forms of integer constant expressions (or address
constants, etc).

I don't think any implementation behaves this way, so either I'm
missing something, or the standard is.

Have you tried gcc -ansi -pedantic ?
 
S

Shao Miller

Shao said:
Shao Miller wrote:

So let's please go on to:

char ** helper;

enum foo {
bar = *helper + 1 - *helper
};

int main(void) {
return 0;
}

That one has the same problem as

int main(void)
{
int x;

x - x;
return 0;
}

In your example, 'x' is not initialized, and I agree that such is a
concern. In the example just above your example, 'helper' is at file
scope[6.9.2p2]. What is the problem, exactly?

I read it wrong. Sorry about that.

No problem.
But, if (helper) is equal to NULL,
which I believe is the case,
then (*helper) is undefined.

I agree that the initially stored value for 'helper' would be comparable
with 'NULL'. I also agree that if '*helper' was evaluated while
'helper' has a null pointer value, then there'd be undefined behaviour.
But even if (*helper) was defined,
then (*helper + 1 - *helper) is still not a constant expression
and the compiler that I'm using,
won't compile the above program.

May I ask which compiler, please? Since I am concerned about
portability, that'd be great to know. Thanks!
 
H

Harald van Dijk

6.6p10 allows 'other forms of constant expressions' plural,

It would be plural both with your interpretation as with mine.
which
also includes the different subcategories of constant expression.

It doesn't say that in the standard, though I was already agreeing
that that seems to be what is intended. This would clearly not be the
intended interpretation for something like "signed integer types",
though: permission to extend the definition of "signed integer type"
should not implicitly allow implementations to extend the definition
of "standard signed integer type".[*] Without explicit permission to
redefine "integer constant expression", an implementer does not have
that luxury either.
One reason for doing this is so implementations can define things
like the 'offsetof' macro using an ordinary expression and still
have that expression be an integer constant expression.

I know, I've actually had the opportunity to modify one compiler to
accept extended forms of integer constant expressions: it was so
strict, that it was impossible to define offsetof as an i.c.e., even
though it was more than capable of simplifying the common forms of
offsetof at translation time. FWIW, I opted for a "literal" keyword to
mark the following expression as an extended constant expression, so
that you could do:

typedef char __char;
typedef size_t __size_t;
#pragma keyword __offsetof_magic for keyword literal
#define offsetof(s, m) (__offsetof_magic (__size_t) ((__char *) &((s
*) 0)->m - (__char *) 0))

-- please excuse possible typos -- which resulted in errors at a later
stage if the expression was too complex to simplify.
Also, if you look at the description of 'constant expression' (as
opposed to the subcategories) all the restrictions on it are
either syntactic or constraints.  Violating either of those would
necessitate a diagnostic message, in which case 6.6p10 would
serve no purpose, since the same result could be achieved by just
defining an extension.

6.6p10 would allow

char p[2];
int i = (int) p;

as an extension, because it would violate none of the relevant
"shall"s in 6.6: (int) p would be a constant expression of type int,
without being an integer constant expression. 6.7.8p4 requires a
constant expression, so without 6.6p10, it would be a constraint
violation.
If 6.6p10 applied only to constant expressions and not to integer
constant expressions what you say would be right, but 6.6p10 also
is meant to allow (and does allow, as I read it) implementations
to accept other forms of integer constant expressions (or address
constants, etc).

It is only on the "and does allow" that we disagreee.
Have you tried   gcc -ansi -pedantic  ?

Yes. Do you know of any constant expression of integer type that is
accepted in file scope initialisers, but not in enumeration constants?
Because I don't. Older versions of gcc accepted many extended forms of
constant expressions in both contexts, current versions accept few if
any in either context.

[*] The "signed integer types" section is worded much better, and
clearly only allows implementation-defined "extended signed integer
types". I'm wondering how we would interpret the standard if it said

"There are five /standard signed integer types/, designated as signed
char, short
int, int, long int, and long long int. (These and other types may be
designated
in several additional ways, as described in 6.7.2.) There may also be
other
implementation-defined signed integer types."
 
S

Shao Miller

[...some code...]
Undefined behaviour? This code is non-compilable, since enum value
specifiers are required to be integer constant expressions (ICE) and
your expression is not an ICE. ICE is not allowed to contain any address
arithmetic, even if the addresses are constant.

If your compiler somehow lets it through, it must be an extension, so
from the standard language point of view the behavior is
[...some more code...]
Also non-compilable for the very same reasons. The same applies to all remaining examples.
char ** helper;

struct s {
int x;
short y;
int z;
};

enum foo {
bar = sizeof (char[
(char *)&((struct s *)*helper)->z -
(char *)((struct s *)*helper)
])
};

int main(void) {
return 0;
}

No hope here either. Array size in this context is also required to be
an ICE.

If an ICE is special case of 6.6p1's "constant-expression," how many
operands does a "constant-expression" have? I count 1:
"conditional-expression." Does your response suggest that _any_
operands for _any_ contained subexpressions are restricted by 6.6p6, for
strictly conforming code? How about in (#A):

int x;

enum foo {
bar = (x - x) ? 3 : 5
};

int main(void) {
return 0;
}

And, perhaps more importantly (#B):

int func(void);

enum foo {
bar = 1 ? 3 : func()
};

int main(void) {
return 0;
}

In #A, 'x' is not an ICE. Is 'x - x' an ICE? Is the containing
conditional-expression an ICE?

In #B, 'func()' is clearly not one of 6.6p6's items, but is permitted
(for "constant-expression", but not necessarily ICE) by 6.6p3's "that is
not evaluated." Would you agree?
 
S

Shao Miller

I should have said 'not an "integral constant expression"'

Ok. I took that as your intended meaning, actually.
One of the ancient ones.

Microsoft Developer Studio 97
VC++ 5.0

Compiling...
new.c
C:\Program Files\DevStudio\SharedIDE\bin\new.c(5) : error C2057:
expected constant expression
Error executing cl.exe.

new.obj - 1 error(s), 0 warning(s)

Do you suppose it's compiling according to some C Standard, or is it
compiling as C++?

Also, if it's not a complete waste of your time, how does it react to:

int x;

enum foo {
bar = 1 ? 3 : x - x
};

int main(void) {
return 0;
}

?
 
S

Shao Miller

Good day, folks. I'm pondering 'offsetof()'. 6.6 "Constant
expressions" might be relevant.

Suppose we've:

int helper[2];

enum foo {
bar = helper + 1 - helper
};

int main(void) {
return 0;
}

Does that code include any undefined behaviour?

Yes.

6.6p6:
An integer constant expression shall have integer type and shall only
have operands
that are integer constants, enumeration constants, character
constants, sizeof
expressions whose results are integer constants, and floating
constants that are the
immediate operands of casts. Cast operators in an integer constant
expression shall only
convert arithmetic types to integer types, except as part of an
operand to the sizeof
operator.

Address constants are not permitted in integer constant expressions.

Do you mean because of the restriction on "operands," above? If so,
which operands in the code are address constants? If your answer is
'helper', is it:
- An operand to the binary addition operator (and the binary subtraction
operator)
- An operand of the integer constant expression?
- Both?

If your answer is "both," does that mean that "operandness" applies to
any and all subexpressions? If so, does that mean that:

int x;

enum foo {
bar = 1 ? 3 : x - x
};

int main(void) {
return 0;
}

is not permitted (you said, "...are not permitted...", above) because 'x
- x' is not one of the items of 6.6p6? Or is 'x - x' one of the items
in 6.6p6? Or is that construction permitted due to some other reason?
This is not a constraint, so a compiler is free to accept it without
any notice, but the behaviour is undefined.

Is the compiler free to reject it? My impression is that you are
suggesting that it is. Since I'm concerned about portability, this is
relevant. A good objective with regard to that would be to aim for
strictly conforming code.
 
S

Shao Miller

Hee hee hee.
Here's one that I like:

/* BEGIN new.c */

#include<stdio.h>

int main(void)
{
char array[][1 + sizeof (void*)] = {0};
enum foo {
bar = sizeof *(1 ? 0 : array)
};

printf("bar is %u\n", (unsigned)bar);
return 0;
}

/* END new.c */

Compiling with GCC, the compiler complained to me that there were
missing braces around the initializer. When I used:

char array[][1 + sizeof (void*)] = {{0}};

it liked that better.

So if I understand your code correctly, 'array' "decays" from:

char[1][1 + sizeof (void *)]

to:

char(*)[1 + sizeof (void *)]

and then the null pointer constant '0' is converted to a null pointer
with type 'char(*)[1 + sizeof (void *)]'. And then the unary
indirection operator '*' is applied to that and is a subexpression with
type 'char[1 + sizeof (void *)]'. The 'sizeof' is then applied to that
and ought to yield '1 + sizeof (void *)' as the value for 'bar'.

This seems to allow for using 'array' to produce an integer constant
expression, even though:
- It will not yield an address constant if evaluated.
- It is not evaluated.
- 6.6p6 suggests some restrictions on operands, and 'array' is an
operand [of a subexpression] that doesn't seem to match those restrictions.

Very nice. :)
 

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,755
Messages
2,569,537
Members
45,020
Latest member
GenesisGai

Latest Threads

Top