Program layout in memory (is anything overwriting my static pointer?)

J

James Kuyper

Nate Eldredge wrote:
....
... a null pointer constant (7.17 (3)), which is
defined in 6.3.2.3 (3) as an integer constant expression with the value
0.

"... or such an expression, cast to type void*".
 
R

Richard Bos

Wolfgang Draxinger said:
That is not to argue about. However some systems may define
NULL != 0,

No, they may not.
even though such pointers would be not null pointers
in the sense of the C standard.

Such systems would not be C systems, period.

Richard
 
W

Wolfgang Draxinger

Nate said:
If we had a machine where address 0 was legal, and we wanted to
use 0xdeadbeef as the null pointer instead, then AIUI a
conforming implementation would still have to define NULL as 0
or (void *)0 or some variant, and arrange that any attempt to
assign the constant value 0 to a pointer resulted in it having
the physical value 0xdeadbeef, and that the physical value
0xdeadbeef would compare equal to the constant value 0.

In such system, how can one then access address 0?

Making the compiler silently replace the value written in the
text with some other value is something no compiler should do.
And in the case of pointers it may break pointer arithmetic
severely, or take a lot of conditionals (in the compiler) to get
it right.

What you suggest is, that there are two sets of addresses, the
set of addresses "visible" to the source code, and the set of
addresses at execution, and that the mapping is not surjective.

Wolfgang Draxinger
 
C

Chris Dollin

Wolfgang said:
In such system, how can one then access address 0?

int iz = 0;
int *zero = (int *) iz;
... *zero ...

Certainly a C implementation on such a machine will make
`(int *) someNonLiteralZero` do the Right Thing -- or suffer
Darwin's razor.
Making the compiler silently replace the value written in the
text with some other value is something no compiler should do.

Rubbish. Every compiler does this: they replace the textual
representations of values with the apropriate machine
representations. The textual representation of a null pointer
constant (which is /spelled/ as a suitably-cast constant zero)
is replaced by the machine representation (which, by hypothesis,
is a pointer with bitwise value 0xdeadbeef).
And in the case of pointers it may break pointer arithmetic
severely, or take a lot of conditionals (in the compiler) to get
it right.

Or, as is actually the case, by neither, unless your "lot" is
more than (roughly) two.

For pointer arithmetic, in `p + i` it's UB if `p` is null, so
the representation of null doesn't matter.

The compiler just has to treat a pointer appearing in a
conditional context, or the null pointer constant appearing
in pointer context, a little differently -- just as it would
on a machine where pointers and integers were stored in
different registers or had different instructions that
operated on them.
What you suggest is, that there are two sets of addresses, the
set of addresses "visible" to the source code, and the set of
addresses at execution, and that the mapping is not surjective.

The mapping is no less surjective [1] than it would be on a machine
where null is all-bits-zero. The null pointer needs /some/
bitwise representation distinct from all other legal object
addresses; whether it's 0x00000000, 0xdeadbeef, or 1xredherring,
is a matter for local implementation convenience.

[1] The surjectivity is absent anyways, since there aren't any
addresses "visible" in the source code, and the number of
addresses that a program might see at run-time depends on [2]
the data it handles and the implementation it's running on.

[2] Or /can/ depend on, depending on how picky one wants to be.
 
F

Flash Gordon

Wolfgang Draxinger wrote, On 10/10/08 09:57:
In such system, how can one then access address 0?

If it creates an object at that address (auto, static, allocated) then
that is its problem and you don't need to specify address 0 in your
code. If it is some special magic location then you use whatever
implementation specific method it provides to accomplish that
implementation specific task.
Making the compiler silently replace the value written in the
text with some other value is something no compiler should do.

It isn't. 0 is just a way of spelling the null pointer constant and is
not a memory address as far as C is concerned. Anyone needing to access
specific memory addresses is deep in the realms of implementation
specific stuff so an implementation specific method is not a problem.
And in the case of pointers it may break pointer arithmetic
severely, or take a lot of conditionals (in the compiler) to get
it right.

Not any more than otherwise. You can't compare pointers to different
objects anyway (apart from for equality) or, in general generate and do
arithmetic at specific addresses.
What you suggest is, that there are two sets of addresses, the
set of addresses "visible" to the source code,

Which in terms of guaranteed addresses is the empty set. The entire
mapping of integers to addresses is implementation defined.
and the set of
addresses at execution, and that the mapping is not surjective.

It is implementation defined, and the only reason to need specific
addresses is when you are doing implementation specific stuff, so you
need to read the implementation documentation anyway.
 
R

Richard Tobin

Wolfgang Draxinger said:
In such system, how can one then access address 0?

Making the compiler silently replace the value written in the
text with some other value is something no compiler should do.

Imagine a language like C, but with the following change: instead
of using the macro "NULL" or an integer constant 0 (possibly cast
to a pointer type) as the null pointer, you have to use the keyword
"nil".

Obviously on such a system pointers with nil assigned to them could
be represented in any way the compiler chose, so long as it wasn't
the same as a legal address. It could perfectly well be 0xffffffff
or anything else suitable for the system.

Wh should it be any different in C? The only problem is that C's
syntax "uses up" the obvious way of getting a pointer whose value
is zero. As others have pointed out, you could probably use a
non-constant zero cast to a pointer type for this.
And in the case of pointers it may break pointer arithmetic
severely, or take a lot of conditionals (in the compiler) to get
it right.

You can't do pointer arithmetic on null pointers. And provided
that the representation of a null pointer isn't the same as a legal
address (including one-past-the-end of an array, probably) then
you can't get a null pointer by legal arithmetic on other pointers.
What you suggest is, that there are two sets of addresses, the
set of addresses "visible" to the source code, and the set of
addresses at execution, and that the mapping is not surjective.

It's true that if a machine allows access to all address values, then
one of them will have to clash with the null pointer representation.
However this is not likely to be true in general-purpose computers,
since the operating system can perfectly well decree that some
addresses are not available for user programs.

-- Richard
 
J

James Kuyper

Wolfgang said:
In such system, how can one then access address 0?

That's up to the implementation. Possibly by converting a non-constant
integer expression with a value of 0 into a pointer; the standard does
require that the result be a null pointer; that guarantee only applies
to integer constant expressions with a value of 0.

Possibly by converting an implementation-defined non-zero integer into a
pointer.

If the address of 0 has special meaning (as seems likely on such a
machine), a pointer to that location could possibly be generated by
writing &__special_object_name, or __special_pointer, or by calling a
function which returns a pointer to that object when given the right
arguments.
Making the compiler silently replace the value written in the
text with some other value is something no compiler should do.

You seem to be quite confused about the relationship between the text of
a program and the representation of the values specified by that text.
It is the norm, not the exception, for such replacement to take place.
The literals 12345, "12345", and 12345.0 all correspond to very
different bit patterns when stored in memory; the same is true of
(void*)12345, assuming that it even represents a valid pointer value.
The standard specifies precisely what happens for string literals,
imposes strict requirements for unsigned integer literals, allows a
little more freedom for signed integer literals, allows a lot of freedom
for floating point literals, and leaves the handling of the conversions
of integer values to pointer types almost completely unspecified, except
when the integer is an integer constant expression with a value of 0.
And in the case of pointers it may break pointer arithmetic
severely, or take a lot of conditionals (in the compiler) to get
it right.

You can't legally do pointer arithmetic on null pointers. Why would the
address(es) used to represent null pointers have any impact on pointer
arithmetic?
What you suggest is, that there are two sets of addresses, the
set of addresses "visible" to the source code, and the set of
addresses at execution, and that the mapping is not surjective.

Not really. The source code never contains addresses. It can contain
null pointer constants that are treated as null pointers, and it can
specify conversion of integer values to pointers; that's probably what
you're thinking of code containing addresses. It can also specify the
conversion of a string into a pointer, using sscanf(); I suppose you
might also think of that as code which contains an address.

What happens in each of those cases is up to the implementation, the
standard imposes no requirements on the resulting pointer for any of
those cases except null pointer constants. The standard was deliberately
written this way, because real systems existed when the standard was
first written which don't do things the simple way you apparently
expect, and such systems are still in use today.

Incidentally, what the standard says about null pointer constants does
not prevent the mapping from being surjective on machines where a null
pointer cannot refer to address 0. It's perfectly permissible to have
some other integer value which does convert to a pointer which refers to
address 0; that integer value just can't be 0, at least not when the
integer is an integer constant expression.
 
J

James Kuyper

James Kuyper wrote:
....
That's up to the implementation. Possibly by converting a non-constant
integer expression with a value of 0 into a pointer; the standard does

I meant to insert the word "not" here. There's a slight :-} difference
in meaning.
 
W

Wolfgang Draxinger

James said:
That's up to the implementation. Possibly by converting a
non-constant integer expression with a value of 0 into a
pointer; the standard does require that the result be a null
pointer; that guarantee only applies to integer constant
expressions with a value of 0.

Possibly by converting an implementation-defined non-zero
integer into a pointer.

If the address of 0 has special meaning (as seems likely on
such a machine), a pointer to that location could possibly be
generated by writing &__special_object_name, or
__special_pointer, or by calling a function which returns a
pointer to that object when given the right arguments.


You seem to be quite confused about the relationship between
the text of a program and the representation of the values
specified by that text. It is the norm, not the exception, for
such replacement to take place. The literals 12345, "12345",
and 12345.0 all correspond to very different bit patterns when
stored in memory;

Nope I'm not. This was not about "I expect text being translated
literally". It's about a definition of comilers "a compiler
translates text into a text of other language, grammer and/or
vocabulary, while keeping the result equivalent to the source
text." At least this is it, to what the topic boils down to in
both "Modern Compiler Design" and "Compilers - Principles,
Techniques, & Tools".

Thus I expect a compiler to convert literals into the target's
representation, without replacing the actual values silently.
When it comes down to the raw bits, a pointer is merely another
piece of data stored somewhere, either RAM, register or
something else, and the values I can find there, if I('d) tap
into it -- with say a JTAG -- should be the values I wrote in
the source code, in the system's (native) representation of
course.

So writing

int *a = 0;

I expect the compiler to turn that into code, that stores the
value 0 in system representation at that place in memory,
where "a" resides. 0 and not some other magic value.
Not really. The source code never contains addresses. It can
contain null pointer constants that are treated as null
pointers, and it can specify conversion of integer values to
pointers; that's probably what you're thinking of code
containing addresses.

Uh, what about memory mapped I/O from/to explicit defined
addresses? Somewhere in the code you've to write the address as
a constant then. Say you got some 16 bits port mapped at address
0xe001ff00, you could write that as

#include <stdint.h>
volatile uint16_t * const port = (uint16_t*) 0xe001ff00;

and access it by

*port = 0xfeed;

uint16_t read = *port;

Now assume, just assume, that some processor has it's mmap-I/O
from addresses 0x00 to 0xff. And assume further, that those
ports are mapped contigously. Like say some kind of bulk shift
register stream multiplexer that takes it's data from a series
of ports. Then you could assume the whole thing as an array,
starting at address 0x0 (yikes 1), and you've to do pointer
arithmetic starting from 0x0 (yikes 2), since C array access
always comes down to that.

Wolfgang Draxinger
 
C

Chris Dollin

Wolfgang said:
Thus I expect a compiler to convert literals into the target's
representation, without replacing the actual values silently.

If it does the former, it /must/ replace the "actual values".

And what the "actual values" are supposed to be is defined by
the language.
When it comes down to the raw bits, a pointer is merely another
piece of data stored somewhere, either RAM, register or
something else, and the values I can find there, if I('d) tap
into it -- with say a JTAG -- should be the values I wrote in
the source code, in the system's (native) representation of
course.

Well ... no. Unless "in the system's (native) representation of
course" has so much looseness as to be useless.

If I write `1` as a literal in a Pop11 program, you typically
won't see a `1` in the translated code; more likely a `5`.
(Yes, Pop11 isn't C, but it does compile code, and your
comments above are general, not C-specific.)

You're putting constraints on the compiler that aren't required
and which can be actively counter-productive.
So writing

int *a = 0;

I expect the compiler to turn that into code, that stores the
value 0 in system representation at that place in memory,
where "a" resides. 0 and not some other magic value.

No. Specifically, in the case of C, no. The Standard says quite
specifically that that `0` is a null pointer constant, and that
it denotes the null pointer in a pointer context. It does not
specify the machine implementation of the null pointer; it just
requires that the null pointer constant translate to the null
pointer in the right context.
Uh, what about memory mapped I/O from/to explicit defined
addresses?

What about it?
Somewhere in the code you've to write the address as
a constant then.

Not necessarily.

(C doesn't have address constants as such, of course, but
let's leave that aside, assuming that the implementation
does cast-int-to-pointer in the "natural" way rather than
eg mapping every int to address 17.)

(a) The memory-mapped IO area could be given a name, like
`fred`, `jim`, or `sheila`, declared as an suitable `extern`
in a header file, with the linker filling in the actual
address.

(b) The addresses may be specified by macros which do
sums to get the actual address values, rather than the
literal address values appearing in the source.
Say you got some 16 bits port mapped at address
0xe001ff00, you could write that as

#include <stdint.h>
volatile uint16_t * const port = (uint16_t*) 0xe001ff00;

and access it by

*port = 0xfeed;

uint16_t read = *port;

(Or by `extern unit16_t hunger_port; ... hunger_port = 0xfeed`.)
Now assume, just assume, that some processor has it's mmap-I/O
from addresses 0x00 to 0xff. And assume further, that those
ports are mapped contigously. Like say some kind of bulk shift
register stream multiplexer that takes it's data from a series
of ports. Then you could assume the whole thing as an array,
starting at address 0x0 (yikes 1),

Why "yikes"?
and you've to do pointer
arithmetic starting from 0x0 (yikes 2),
Ditto.

since C array access always comes down to that.

Yes. And?
 
W

Wolfgang Draxinger

Chris said:
What about it?

It get's obscure, if "funny" things happen to pointers. Also many
C oriented APIs are designed on the assumption (at least I take
it that way, reading those APIs), that pointers have a 1:1
correspondence to address space, either virtual or absolute.

Of course the authors of those APIs are to blame for problems,
that may arise from that; though that's unlikely on current
systems.
(a) The memory-mapped IO area could be given a name, like
`fred`, `jim`, or `sheila`, declared as an suitable `extern`
in a header file, with the linker filling in the actual
address.

True, but oftenly not done that way. Especially when doing uC
programming, where text size (text in the meaning of machine
program text), addresses are put into the code by casting int to
pointer, to enable the compiler to perform arithmetic
optimization on the assembler level, which might replace whole
code sections with a single instruction then. At the linking
stage such optimization would be difficult to perform, though
not impossible.
Why "yikes"?
(...)
Ditto.

Both problematic with the C standard, as I see/saw it, given the
previous posts.

Wolfgang Draxinger
 
J

jameskuyper

Wolfgang said:
....
Nope I'm not. This was not about "I expect text being translated
literally". It's about a definition of comilers "a compiler
translates text into a text of other language, grammer and/or
vocabulary, while keeping the result equivalent to the source
text." At least this is it, to what the topic boils down to in
both "Modern Compiler Design" and "Compilers - Principles,
Techniques, & Tools".

Thus I expect a compiler to convert literals into the target's
representation, without replacing the actual values silently.

Your expectation is not consistent with the way the committee
deliberately chose to define the C language. What the standard says
is:

"An integer may be converted to any pointer type. Except as previously
specified, the result is implementation-defined, might not be
correctly aligned, might not point to an entity of the referenced
type, and might be a trap representation."

Pay close attention, in particular, to the words "implementation-
defined".
Uh, what about memory mapped I/O from/to explicit defined
addresses? Somewhere in the code you've to write the address as
a constant then. Say you got some 16 bits port mapped at address
0xe001ff00, you could write that as

#include <stdint.h>
volatile uint16_t * const port = (uint16_t*) 0xe001ff00;

and access it by

*port = 0xfeed;

uint16_t read = *port;

Now assume, just assume, that some processor has it's mmap-I/O
from addresses 0x00 to 0xff. And assume further, that those
ports are mapped contigously. Like say some kind of bulk shift
register stream multiplexer that takes it's data from a series
of ports. Then you could assume the whole thing as an array,
starting at address 0x0 (yikes 1), and you've to do pointer
arithmetic starting from 0x0 (yikes 2), since C array access
always comes down to that.

Every important aspect of that code is at least implementation-
defined, or unspecified, or has potentially undefined behavior. The C
standard deliberately fails to guarantee that (uint16_t*) 0xe001ff00
produces a pointer that points at the memory location identified by an
address of 0xe001ff00. It's might do so, but it's not guaranteed to do
so, and there are real systems where it won't. In fact, the standard
doesn't even guarantee that it produces a legal pointer value; if it
is a trap representation, the behavior of your code is undefined.

If you need to know how to create a pointer that points at a
particular memory location, you'll have to read your compiler's
documentation to determine how to do that; if you use a different
compiler for the same platform, you might find the method you had to
use with the first compiler won't produce the same result with the
second one. In practice, the market tends to discourage the creation
of compilers for the same platform that are incompatible in this way;
but the standard doesn't prohibit it. More importantly, there's
nothing, not even the market, to discourage compilers for different
platforms from being incompatible in this way.
 
J

jameskuyper

Wolfgang Draxinger wrote:
....
It get's obscure, if "funny" things happen to pointers. Also many
C oriented APIs are designed on the assumption (at least I take
it that way, reading those APIs), that pointers have a 1:1
correspondence to address space, either virtual or absolute.

No, the C standard allows for a many:1 relationship, so long as all of
the different pointers that point at the same location compare equal.
C also makes that many:1 relationship implementation-defined.
Furthermore, you're not just talking about the relationship of
pointers and address spaces, you're also talking about the
relationship between integers and pointers. That relationship is also
implementation-defined, and the standard only imposes two requirements
on that relationship: if the implementation has an integer type that
is big enough, conversion of a pointer to that integer type and back
to the original pointer type must produce a pointer that compares
equal to the original pointer. If stored in a pointer object, that
value need not have the same representation as the original pointer,
but if it has a different representation, the compiler must implement
pointer comparisons in such a way that the two different
representations compare equal. In C99, if a sufficiently big integer
type exists, then there will be types named intptr_t and uintptr_t for
signed and unsigned integer types with that characteristic. <stdint.h>
defines macros named INTPTR_MAX and UINTPTR_MAX which can used to
determine, at compile time, whether or not such a type exists.

Notice that the standard does not guarantee anything the other way
around: if you convert an integer to a pointer type and back to the
original integer type, you're NOT guaranteed to get the same integer
value.

The only other requirement that the standard imposes is due to the
fact that an integer constant expression with a value of 0 is a null
pointer constant. If such an expression is converted to pointer type,
the result is required to be a null pointer. That's true, regardless
of whether or not it is represented by a pointer with all bits zero,
and regardless of whether or not that pointer points at a memory
location with an address of zero.
 
K

Keith Thompson

Wolfgang Draxinger said:
In such system, how can one then access address 0?

Making the compiler silently replace the value written in the
text with some other value is something no compiler should do.
And in the case of pointers it may break pointer arithmetic
severely, or take a lot of conditionals (in the compiler) to get
it right.
[...]

In *most* C compilers (every one I've ever used), a null pointer is
represented as all-bits-zero.

But if an implementation chooses to represent a null in some other
way, then it *must* interpret a literal 0 as a null pointer constant.

Please read section 5 of the comp.lang.c FAQ, <http://www.c-faq.com/>.
 
V

vippstar

Wolfgang Draxinger said:
Nate Eldredge wrote:
In such system, how can one then access address 0?
Making the compiler silently replace the value written in the
text with some other value is something no compiler should do.
And in the case of pointers it may break pointer arithmetic
severely, or take a lot of conditionals (in the compiler) to get
it right.

[...]

In *most* C compilers (every one I've ever used), a null pointer is
represented as all-bits-zero.

But if an implementation chooses to represent a null in some other
way, then it *must* interpret a literal 0 as a null pointer constant.

Please read section 5 of the comp.lang.c FAQ, <http://www.c-faq.com/>.

Here's a message by Harald van Dijk (that's how his name appears to be
in ASCII)
Message-ID: <[email protected]>
He claims to have had access to a system where the null pointers
representation was 0x55555555.
Mentioning this so others don't think such systems are imaginary...

Also, here's a test program

#include <stdio.h>
#include <string.h>

int main(void) {

void *p = {0};
unsigned char s[sizeof p] = {0};
printf("A null pointer stored on char * and void *, ");
printf("%s all bits zero in this implementation\n", memcmp(p, s,
sizeof p) ? "isn't" : "is");
return 0;
}


For all the rest pointers, you'd have to test individually. (except
structure and union pointers, which have the same size and
representation, like char * and void *, that however, doesn't mean
char * and a structure pointer has the same representation)
It's allowed by the standard for int * to have 0xdeadbeef as a null
pointer, while char * has all-bits-zero.

This might not seem like a problem you'd fall on to, but it's common
to see calloc() in code to allocate pointers, which are assumed to be
initialized to NULL; (because calloc zeroes the region of memory it
allocates)
It's best to have a loop to initialize each pointer to a null pointer.

Google says:
An error was encountered while trying to post, please try again later.
In case this message appears twice.
 
W

Wolfgang Draxinger

Notice that the standard does not guarantee anything the other
way around: if you convert an integer to a pointer type and
back to the original integer type, you're NOT guaranteed to get
the same integer value.

This raises the following question (The answer is probably in the
standard). I formulate it like the "ISSUEs" of OpenGL
specification:

Q: If a integer converted to a pointer converted to an integer
may be different to the original value, does that mean, that a
constant integer 0, converted to a (null) pointer and back to an
integer may have not value 0.

Just curious,

Wolfgang Draxinger
 
J

jameskuyper

Wolfgang said:
This raises the following question (The answer is probably in the
standard). I formulate it like the "ISSUEs" of OpenGL
specification:

Q: If a integer converted to a pointer converted to an integer
may be different to the original value, does that mean, that a
constant integer 0, converted to a (null) pointer and back to an
integer may have not value 0.

Correct. The relevant text from the standard is 6.3.2.3p6:
"Any pointer type may be converted to an integer type. Except as
previously specified, the
result is implementation-defined. If the result cannot be represented
in the integer type,
the behavior is undefined. The result need not be in the range of
values of any integer
type."

What's not immediately obvious is the fact that no previous
specification covers this case (neither does any later one). As a
result, what this clause says about the result of the conversion back
to an integer is the ONLY thing the standard says about that
conversion; and it does not say that the result is 0.
 
V

vippstar

On Oct 10, 6:57 pm, (e-mail address removed) wrote:
Also, here's a test program

#include <stdio.h>
#include <string.h>

int main(void) {

void *p = {0};
unsigned char s[sizeof p] = {0};
printf("A null pointer stored on char * and void *, ");
printf("%s all bits zero in this implementation\n", memcmp(p, s,
sizeof p) ? "isn't" : "is");

That should've been &p, in the memcmp call.
 
W

Wolfgang Draxinger

What's not immediately obvious is the fact that no previous
specification covers this case (neither does any later one). As
a result, what this clause says about the result of the
conversion back to an integer is the ONLY thing the standard
says about that conversion; and it does not say that the result
is 0.

I suspected this. Which brings me to a (hopefully) final
question: If one writes

sometype *p = ...;

if(p){...}

while(!p){...}

for(...;p;...){...}

and so on, is that evaluated from a pointer point of view (i.e.
pointer being (not) null), or if the pointer is implicitly cast
to integer. If the later was the case, then only explicitly
testing NULL != p, or NULL == p would be robust, which brings us
back to the original topic, of how a pointer should be tested
correctly.

Wolfgang Draxinger
 
K

Keith Thompson

Wolfgang Draxinger said:
I suspected this. Which brings me to a (hopefully) final
question: If one writes

sometype *p = ...;

if(p){...}

while(!p){...}

for(...;p;...){...}

and so on, is that evaluated from a pointer point of view (i.e.
pointer being (not) null), or if the pointer is implicitly cast
to integer. If the later was the case, then only explicitly
testing NULL != p, or NULL == p would be robust, which brings us
back to the original topic, of how a pointer should be tested
correctly.

As has already been said several times, when a pointer value is used
in a context that requires a condition, the pointer value is
implicitly compared to 0. The implicit 0 is treated as a null pointer
constant; there is no pointer-to-integer conversion. And the "!"
operator yields 1 if its operand compares equal to 0, 0 otherwise.

So, assuming p is a pointer, the following are exactly equivalent,
regardless of the implementation's chosen representation for a null
pointer:

if (p)
if (p != NULL)

while (!p)
while (p == NULL)

for (...; p; ...)
for (...; p != NULL; ...)

One more time: have you read section 5 of the comp.lang.c FAQ? It has
an excellent discussion of null pointers, and would have answered most
of your questions.
 

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,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top