Interview questions

N

Niklas Borson

E. Robert Tisdale said:
Should be

const void* _vptr;

No, but you're right about the const. It should be:

Foo_VTable const* _vptr;
Should be

void (*dtor)(const Foo*);

You need to be able to destroy const objects
as well as variable objects with the same destructor!

Yes, if you want to following the example of C++, but
if course if you're reinventing the wheel you have the
option of changing the rules. :)
Should be

void Foo_dtor(const Foo*);

Yes. Same as above.
#include "foo.h"

// Actually, the struct Foo_VTable definition
// belongs here and *not* in the public header file.

No, because the user of the class needs to know the layout
of the vtable structure in order to call pseudo-virtual
functions (see example below).
Should be

foo._vfptr->dtor(foo);

Doh! Like I said, I didn't try to compile it. But this example
shows clearly why _vfptr can't just be a const void*, and why
the user of the class needs access to the definition of
Foo_VTable.
Should be

foo->_vfptr->dtor(foo);

Yes, same as above.
Now suppose that you want to *derive* Bar from Foo

Then Bar would have to have its own Bar_VTable structure
definition whould would have the same members as Foo_VTable
plus possibly some additional members. It too would have to
be public, and each isntance of Bar would point to a static
const instance of Bar_VTable.
 
E

E. Robert Tisdale

Niklas said:
No, but you're right about the const. It should be:

Foo_VTable const* _vptr;




Yes, if you want to following the example of C++ but,
of course, if you're reinventing the wheel,
you have the option of changing the rules. :)

But you don't have the option of failing to destroy const objects.
They may contain pointers to memory allocated dynamically
and you will need to free that memory unless you want memory leaks.
Yes. Same as above.

No. Same as above.
No, because the user of the class needs to know the layout
of the vtable structure in order to call pseudo-virtual
functions (see example below).

No. The user should *not* call the functions out of the vtable directly
but implement *virtual* functions in foo.c or bar.c that do so.
Doh! Like I said, I didn't try to compile it. But this example
shows clearly why _vfptr can't just be a const void*, and why
the user of the class needs access to the definition of
Foo_VTable.

No. See example below.
Yes, same as above.


Then Bar would need to have
its own Bar_VTable structure definition
which would have the same members as Foo_VTable
plus possibly some additional members.

Which my example shows:

// implementation
#include "bar.h"

struct Bar_VTable {
void (*dtor)(const Bar*);
int (*f)(Bar*, int, int);
double (*g)(Bar const*, int);
char* hello(const Bar*);
} Bar_VTable;

const Bar_VTable g_Bar_vtable = {
Bar_dtor,
Bar_f,
Bar_g,
Bar_hello
};
It too would have to be public and each instance of Bar
would point to a static const instance of Bar_VTable.

No!
Bar is *derived* from Foo.
An object of type Bar *is* an object of type Foo.
There is no special syntax in C to express this
so you *must* write:

typedef struct Bar {
Foo foo;
// and, possibly, other data members
} Bar;

An object of type Bar *encapsulates* an object of type Foo.
If, insteas, you write

typedef struct Bar {
Foo foo;
const Bar_VTable* _vptr;
// and, possibly, other data members
} Bar;

You cannot pass a pointer to an object of type Bar
to a function of a pointer to an object of type Foo
and expect it to find pointer member _vptr.

The user should *not* call the functions
out of the virtual function table directly.
Instead, the class library developer should provide
*virtual* functions which do this for the user

int Foo_virtual_f(Foo* _this, int i, int j) {
return ((const Foo_VTable*)(_this->_vptr))->f(_this, i, j);
}

for example.
That way, the actual representation of the virtual function table
remains private to the implementation.
 
N

Niklas Borson

E. Robert Tisdale said:
Niklas said:
E. Robert Tisdale wrote: [snip]
You need to be able to destroy const objects
as well as variable objects with the same destructor!

Yes, if you want to following the example of C++ but,
of course, if you're reinventing the wheel,
you have the option of changing the rules. :)

But you don't have the option of failing to destroy const objects.
They may contain pointers to memory allocated dynamically
and you will need to free that memory unless you want memory leaks.

I'll go along with that. I was thinking of the argument that
comes up from time to time of whether it should be possible
to delete an object through a pointer-to-const. It is possible,
of course, but some people think it shouldn't be.
No. The user should *not* call the functions out of the vtable
directly but implement *virtual* functions in foo.c or bar.c
that do so.

If I understand correctly, you're saying the users of the
class should call a wrapper function which in turn calls
the actual virtual function through the pointer in the
vtable. Yes, one could do this.

Personally, I don't see the advantage of this extra
function call. IMO, exposing the structure of the vtable
does not violate encapsulation. It doesn't expose the
class's data, for example. It tells you only what virtual
functions the class has -- i.e., its interface.

Moreover, anyone who wants to derive from the class
needs to know the structure of the vtable anyway so
as to provide a compatible vtable for the derived
class.
Which my example shows:

// implementation
#include "bar.h"

struct Bar_VTable {
void (*dtor)(const Bar*);
int (*f)(Bar*, int, int);
double (*g)(Bar const*, int);
char* hello(const Bar*);
} Bar_VTable;

const Bar_VTable g_Bar_vtable = {
Bar_dtor,
Bar_f,
Bar_g,
Bar_hello
};

Yes, and note that this means the implementor of Bar needs to
know the structure of Foo_VTable because the members they have
in common have to have the same layout. This to me suggests
that the structure of the vtable is part of the interface of
a class -- at least for derivers of the class, so why not for
users of the class as well. (Of course if you're using C++
the structure of the vtable is compiler-dependant, but it
is still *implied* by the class definition, and is visible to
other C++ code that uses the class.)

As an aside, note that the virtual methods themselves need to
be visible directly (not just through pointers) for a couple
of reasons. First, Bar_dtor should call Foo_dtor.

Second, Bar might want to inherit some virtual methods of Foo.
For example, to inherit f, we'd set the second member of
g_Bar_vtable to Foo_f instead of Bar_f.
No!
Bar is *derived* from Foo.
An object of type Bar *is* an object of type Foo.
There is no special syntax in C to express this
so you *must* write:

typedef struct Bar {
Foo foo;
// and, possibly, other data members
} Bar;

The above is the layout we'd use if we wanted to follow the
typicall C++ approach. The constructor for Bar would be
implemented something like this:

void Bar_ctor(Bar* bar)
{
// Initialize the foo "base class"; as per C++ rules,
// the virtual function pointer will point to the base
// class vtable during base class initialization.
Foo_ctor(&bar->foo);

// Now change the virtual function pointer to point to
// the "derived" class vtable. This requires that the
// vtables have a compatible layout.
bar->foo->_vptr = &g_Bar_vtable;

// Perform other initialization here.
}

Because of the compatible vtable layouts, and because Foo
is the first member of bar, we can say a Bar "is a" Foo.
Of course, a C compiler doesn't know this so a cast is
requird to convert a Bar* to a Foo*.
An object of type Bar *encapsulates* an object of type Foo.
If, insteas, you write

typedef struct Bar {
Foo foo;
const Bar_VTable* _vptr;
// and, possibly, other data members
} Bar;

You cannot pass a pointer to an object of type Bar
to a function of a pointer to an object of type Foo
and expect it to find pointer member _vptr.

Of course not, and I never suggested using the above
representation.
The user should *not* call the functions
out of the virtual function table directly.
Instead, the class library developer should provide
*virtual* functions which do this for the user

int Foo_virtual_f(Foo* _this, int i, int j) {
return ((const Foo_VTable*)(_this->_vptr))->f(_this, i, j);
}

for example.
That way, the actual representation of the virtual
function table remains private to the implementation.

Well, not exactly private. More like "protected" as
implementors of derived classes still need to know
the vtable layout.

Like I said above, you could this. I see nothing wrong
with providing wrapper functions like the above. But
I don't agree that exposing the structure of a vtable
violates encapsulation. It exposes only the set of
virtual functions a class provides -- which is part
of its interface -- not any of the class's actual
data.
 
E

E. Robert Tisdale

Niklas said:
I'll go along with that.

You had me worried.
I was thinking of the argument that comes up from time to time
of whether it should be possible
to delete an object through a pointer-to-const.
It is possible, of course, but some people think it shouldn't be.
The user should *not* call the functions out of the vtable directly
but implement *virtual* functions in foo.c or bar.c that do so.

If I understand correctly, you're saying [that]
the users of the class should call a wrapper function
which in turn calls the actual function
through the pointer in the vtable.
Yes, one could do this.

What I am saying is that this is what your C++ compiler does for you.
A virtual function is actually a wrapper
(that's why it's called a virtual function) that calls
the actual function through the corresponding function pointer
in the virtual function table.
Personally,
I don't see the advantage of this extra function call.
IMO, exposing the structure of the vtable
does not violate encapsulation.
It doesn't expose the class's data, for example.
It tells you only what virtual functions the class has --
i.e., its interface.

No!
It is an implementation detail.
Siemel Naran used an array
to represent the virtual function table.
Moreover, anyone who wants to derive from the class
needs to know the structure of the vtable anyway so
as to provide a compatible vtable for the derived class.

That's because the C computer programming language
does *not* support inheritance.

Yes, the application programmer (user) must know the *order*
in which function pointers appear in struct Foo_VTable
but the application programmer does *not* need the definition
of struct Foo_VTable to define struct Bar_VTable.
My example shows:

// implementation
#include "bar.h"

struct Bar_VTable {
void (*dtor)(const Bar*);
int (*f)(Bar*, int, int);
double (*g)(Bar const*, int);
char* hello(const Bar*);
} Bar_VTable;

const Bar_VTable g_Bar_vtable = {
Bar_dtor,
Bar_f,
Bar_g,
Bar_hello
};

Yes, and note that this means [that]
the implementor of Bar needs to know the structure of Foo_VTable

Well, at least the order in which function pointers appear.
because the members they have in common [must] have the same layout.
This to me suggests that the structure of the vtable
is part of the interface of a class --
at least for derivers of the class,
so why not for users of the class as well.
(Of course if you're using C++,
the structure of the vtable is compiler-dependant,
but it is still *implied* by the class definition,
and is visible to other C++ code that uses the class.)

The structure of a C++ vtable isn't defined
in *any* public C++ header [file].
I don't see any reason why it should be defined
in any public C header file.
What is happening here is that the C programmer
is playing the role of a C++ compiler in simulating inheritance.
The C programmer can and should infer
the structure of the virtual function table from the definition
of class Foo (struct Foo + virtual function declarations).
As an aside, note that the virtual methods themselves need to
be visible directly (not just through pointers) for a couple
of reasons. First, Bar_dtor should call Foo_dtor.

Second, Bar might want to inherit some virtual methods of Foo.
For example, to inherit f, we'd set the second member of
g_Bar_vtable to Foo_f instead of Bar_f.

Run-time polymorphism has been discussed at length
in the comp.lang.c newsgroup. See Google Groups

http://groups.google.com/

and search for

Tisdale Shape group:comp.lang.c.*

Both Re: "class" in C and Re: C version of C++'s virtual functions
contain an ANSI C implementation of Bjarne Stroustrups Shape class.
 
N

Niklas Borson

Sorry I'm slow in replying. I was out of time and offline for a week.
Please do not construe my silence as agreement.

E. Robert Tisdale said:
Niklas said:
E. Robert Tisdale said:
The user should *not* call the functions out of the vtable directly
but implement *virtual* functions in foo.c or bar.c that do so.

If I understand correctly, you're saying [that]
the users of the class should call a wrapper function
which in turn calls the actual function
through the pointer in the vtable.
Yes, one could do this.

What I am saying is that this is what your C++ compiler does for you.

Maybe your compiler does this, but not mine. Here's an example:

class Foo {
public:
virtual ~Foo();
virtual void Hello();
};
void Test(Foo* foo)
{
foo->Hello();
}

And here's the assembly code my compiler generates for Test:

?Test@@YAXPAVFoo@@@Z PROC NEAR
mov ecx, DWORD PTR _foo$[esp-4]
mov eax, DWORD PTR [ecx]
jmp DWORD PTR [eax+4]
?Test@@YAXPAVFoo@@@Z ENDP

The first instruction loads the foo parameter into a register.
The second instruction loads the vtable pointer into a different
register. The third instruction performs a tail call by jumping
to the entry point of the Hello function, which it gets directly
from the vtable.

In short, the Test function accesses the vtable directly without
calling a wrapper function.
A virtual function is actually a wrapper
(that's why it's called a virtual function) that calls
the actual function through the corresponding function pointer
in the virtual function table.

According to Stroustrup, the word virtual means "may be redefined
later in a class derived from this one". Nothing about wrapper
functions there.
No!
It is an implementation detail.

It's an implementation detail of the virtual call mechanism.
That's not the same thing as exposing the representation or
inner workings of the class.
Siemel Naran used an array
to represent the virtual function table.

Which did not work very well because (1) all the function
pointers had to have the same type, and (2) the call site
had to specify the array index of the virtual function
rather than its name.
That's because the C computer programming language
does *not* support inheritance.

Yes, the application programmer (user) must know the *order*
in which function pointers appear in struct Foo_VTable

The user needs to know the order and types of the function
pointers, which is to say *everything* about Foo_VTable.
but the application programmer does *not* need the definition
of struct Foo_VTable to define struct Bar_VTable.

The implementor of Bar_VTable needs to ensure that it has
the same members in the same order as Foo_VTable. Therefore
he needs access to the definition of Foo_VTable, or
equivalent documentation.

He may not actually *reference* that definition in the code,
but that doesn't mean it's hidden or encapsulated in any
meaningful way.
The structure of a C++ vtable isn't defined
in *any* public C++ header [file].

It is implied by the class definition, which is typically in
a header file. That is, the compiler can deduce the vtable
layout from the class definition, though *how* it does so
(or whether there's a vtable at all) is implementation
dependant.

See the example assembler output I gave earlier. To generate
the object code for the Test function, the compiler relied on
its knowledge of the vtable layout for Foo, which it inferred
from the class definition.
I don't see any reason why it should be defined
in any public C header file.
What is happening here is that the C programmer
is playing the role of a C++ compiler in simulating inheritance.

Exactly. The programmer is playing the role of the compiler.
Therefore, things like vtables and virtual calls, which a C++
compiler creates for you, must be coded explicitly in C.
The C programmer can and should infer
the structure of the virtual function table from the definition
of class Foo (struct Foo + virtual function declarations).

What virtual function declarations? There is no such thing in C.
In C++ the compiler determines the vtable layout (or whatever)
based on the virtual function declarations. In C, the programmer
must determine the vtable layout. A vtable structure seems like
a natural and obvious way of doing so.

Yes you could use wrapper functions to make the virtual function
calls, but this is not what a typical C++ compiler does. Moreover,
since implementors of derived classes still need to know both the
virtual call mechanism and the vtable layout for the base class,
hiding the actual declaration of the vtable does not actually
buy you anything in terms of encapsulation.
Run-time polymorphism has been discussed at length
in the comp.lang.c newsgroup. See Google Groups

http://groups.google.com/

and search for

Tisdale Shape group:comp.lang.c.*

I did. Perhaps you didn't notice that you and I both participated
in that thread. (I posted under a different email address then, but
used my real name then and now.) At the time, you argued that it's
impossible to do OOP in C because it's not really OO unless you use
specific language features, e.g., keywords like class and virtual.
I argued to the contrary. Now you appear to have changed your tune,
and our quibble is over merely *how* one might implement virtual
functions in C. (Of course, I'm not saying there's only one way,
just that my way is valid and reasonable. A better way, of course,
is to just use C++.)
 
E

E. Robert Tisdale

Niklas said:
Sorry I'm slow in replying.
I was out of time and offline for a week.
Please do not construe my silence as agreement.

Please do not construe my silence as agreement.
E. Robert Tisdale wrote:

Here's an example:

class Foo {
public:
virtual ~Foo(void);
virtual void Hello(void);
};

void Test(Foo* foo) {
foo->Hello();
}

And here's the assembly code my compiler generates for Test:

?Test@@YAXPAVFoo@@@Z PROC NEAR
mov ecx, DWORD PTR _foo$[esp-4]
mov eax, DWORD PTR [ecx]
jmp DWORD PTR [eax+4]
?Test@@YAXPAVFoo@@@Z ENDP

Did you just forget to show us the vtable?
The first instruction loads the foo parameter into a register.
The second instruction loads the vtable pointer into a different
register. The third instruction performs a tail call
by jumping to the entry point of the Hello function,
which it gets directly from the vtable.

How did your compiler know where to get the entry point for Hello?
In short, the Test function accesses the vtable directly
without calling a wrapper function.

Does it surprise you
that the C++ compiler inline'd the wrapper function?
According to Stroustrup, the word virtual means
"may be redefined later in a class derived from this one".
Nothing about wrapper functions there.

You don't show us a derived class
with a redefined "virtual" function.
How is Stroustup's remark relevant?

Which did not work very well because
(1) all the function pointers had to have the same type, and
(2) the call site had to specify the array index
of the virtual function rather than its name.

I agree that it's ugly and amy invoke "undefined behavior"
as far as the ANSI/ISO C standards are concerned
but it *does* work and it ports everywhere.

The user needs to know the order and types of the function
pointers, which is to say *everything* about Foo_VTable.

Just as the C++ compiler knows these things
*without* any public definition of the virtual function table.

The implementor of Bar_VTable needs to ensure that
it has the same members in the same order as Foo_VTable.
Therefore, he needs access to the definition of Foo_VTable,
or equivalent documentation.

I agree. C programmers must know enough about a Foo_VTable
to define a Bar_VTable correctly.
But neither definition should be published in a public header file.
He may not actually *reference* that definition in the code,
but that doesn't mean [that]
it's hidden or encapsulated in any meaningful way.

Correct!
And that's one very good reason why C++ exists.
C just doesn't provide the protection that is required
to write reliable object oriented programs.
The structure of a C++ vtable isn't defined
in *any* public C++ header [file].

It is implied by the class definition,
which is typically in a header file.
That is, the compiler can deduce the vtable
layout from the class definition, though *how* it does so
(or whether there's a vtable at all) is implementation
dependant.

See the example assembler output I gave earlier.
To generate the object code for the Test function,
the compiler relied on its knowledge of the vtable layout for Foo,
which it inferred from the class definition.
I don't see any reason why it should be defined
in any public C header file.
What is happening here is that the C programmer
is playing the role of a C++ compiler in simulating inheritance.

Exactly. The programmer is playing the role of the compiler.
Therefore, things like vtables and virtual calls, which a C++
compiler creates for you, must be coded explicitly in C.
The C programmer can and should infer
the structure of the virtual function table from the definition
of class Foo (struct Foo + virtual function declarations).

What virtual function declarations?
There is no such thing in C.
In C++ the compiler determines the vtable layout (or whatever)
based on the virtual function declarations.
In C, the programmer must determine the vtable layout.
A vtable structure seems like a natural and obvious way of doing so.

Yes you could use wrapper functions to make the virtual function
calls, but this is not what a typical C++ compiler does. Moreover,
since implementors of derived classes still need to know both the
virtual call mechanism and the vtable layout for the base class,
hiding the actual declaration of the vtable does not actually
buy you anything in terms of encapsulation.

Run-time polymorphism has been discussed at length
in the comp.lang.c newsgroup. See Google Groups

http://groups.google.com/

and search for

Tisdale Shape group:comp.lang.c.*
I did. Perhaps you didn't notice that
you and I both participated in that thread.
(I posted under a different email address then,
but used my real name then and now.)

I didn't realize that.
At the time, you argued that it's impossible to do OOP in C
because it's not really OO unless you use specific language features,
e.g., keywords like class and virtual.

You probably have me confused with someone else.
I argued to the contrary. Now you appear to have changed your tune,
and our quibble is over merely *how* one might implement virtual
functions in C. (Of course, I'm not saying [that]
there's only one way, just that my way is valid and reasonable.
A better way, of course, is to just use C++.)
 
N

Niklas Borson

E. Robert Tisdale said:
Niklas said:
E. Robert Tisdale wrote:

Here's an example:

class Foo {
public:
virtual ~Foo(void);
virtual void Hello(void);
};

void Test(Foo* foo) {
foo->Hello();
}

And here's the assembly code my compiler generates for Test:

?Test@@YAXPAVFoo@@@Z PROC NEAR
mov ecx, DWORD PTR _foo$[esp-4]
mov eax, DWORD PTR [ecx]
jmp DWORD PTR [eax+4]
?Test@@YAXPAVFoo@@@Z ENDP

Did you just forget to show us the vtable?

It wasn't necessary or relevant. The point was to show how the
virtual function call was made at the call site (under this
particular implementation).
How did your compiler know where to get the entry point for Hello?

The compiler has access to the definition of the Foo class. Therefore
it knows that Hello is the second virtual function in the class so it
obtains its address by accessing the second slot in the vtable. In this
case that's the last instruction: jmp DWORD PTR [eax+4]
Does it surprise you
that the C++ compiler inline'd the wrapper function?

I see no reason to suppose that a wrapper function ever existed. I
certainly didn't define it, and can't imagine why the C++ compiler
would define one only to optimize it away later.
You don't show us a derived class
with a redefined "virtual" function.
How is Stroustup's remark relevant?

It's relevant because you had claimed that a virtual function was
a wrapper function, i.e., that that's what virtual meant. Clearly,
that's not what Stroustrup thinks it means, or anyone else as far
as I know.
I agree that it's ugly and amy invoke "undefined behavior"
as far as the ANSI/ISO C standards are concerned
but it *does* work and it ports everywhere.



Just as the C++ compiler knows these things
*without* any public definition of the virtual function table.

As I said, it's implied by the class definition. The compiler
must have access to the class definition in order to know how
to make a virtual function call.
I agree. C programmers must know enough about a Foo_VTable
to define a Bar_VTable correctly.
But neither definition should be published in a public header file.

Why not? You never make a case for this.
He may not actually *reference* that definition in the code,
but that doesn't mean [that]
it's hidden or encapsulated in any meaningful way.

Correct!

Note that "it" in the last sentence you quoted refers to the
vtable, not to the class implementation.

My point is that there's no reason to hide the vtable definition
in an implementation file because users of the class need to
know what it looks like anyway.
And that's one very good reason why C++ exists.
C just doesn't provide the protection that is required
to write reliable object oriented programs.

I agree it makes more sense to do OOP in C++. That's one reason
it exists, after all. However, I also think you're twisting my
words. You *can* hide a "class" implementation in C if you choose.
The only thing you can't effectively hide is the virtual function
call mechanism and the vtables -- again, because what is implicit
in C++ (the vtable/virtual call mechansim) must be explicit in a
C program because of the lack of direct language support for
polymorphism.
You probably have me confused with someone else.

Nope. The thread is still accessible on google if you care to
see for yourself.
 
T

tom_usenet

Reinterpret_cast performs a value conversion. It doesn't modify anything
on the implementation of jk. So the above has essentially no effect. It
is as if you had written


reinterpret_cast<unsigned long&>(jk);

No, reinterpret_casting to a reference type is equivalent to the
pointer version. e.g.

*reinterpret_cast<unsigned long*>(&jk) = 4000000000UL;

which is obviously very bad news. The other examples further down also
look either ill-formed or undefined behaviour.

Tom
 
T

tom_usenet

JKop posted:
This behavior also applies to types other than class types. For
instance, static_cast can be used to convert from an int to a char.
However, the resulting char may not have enough bits to hold the entire
int value. Again, it is left to the programmer to ensure that the
results of a static_cast conversion are safe.

This excerpt seems to be bullshit. Try running the following on your system.
If the above was true, then the end output should be all 255, assuming that
your system is 8-Bit char and 32-Bit long.


#include <iostream>

int main(void)
{
unsigned char jk[4] = { 0, 0, 0, 0 };

std::cout << "jk[0] == " << (int)jk[0]
<< "\r\njk[1] == " << (int)jk[1]
<< "\r\njk[2] == " << (int)jk[2]
<< "\r\njk[3] == " << (int)jk[3]
<< std::endl;

std::system("PAUSE");

static_cast<unsigned long>(jk[0]) = 0xFFFFFFFF;

The above line is ill-formed. Perhaps you meant:
reinterpret_cast said:
static_cast isn't as special as I thought. I thought it would've been able
to achieve what unions do. Ah well!

That's what reinterpret_cast can do.

Tom
 
T

tom_usenet

tom_usenet posted:
static_cast<unsigned long>(jk[0]) = 0xFFFFFFFF;

The above line is ill-formed. Perhaps you meant:
reinterpret_cast<unsigned long&>(jk[0]) = 0xFFFFFFFF;


Try compile that.

It compiles fine (on 2 highly standards compliant compilers). The
static cast version doesn't of course, since you're trying to assign a
value to an rvalue.
See my thread entitled "accessor_cast".

Will do.

Tom
 

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,999
Messages
2,570,243
Members
46,835
Latest member
lila30

Latest Threads

Top