C++ exception handling is defective

K

Kai-Uwe Bux

Zorro said:
Kai-Uwe Bux said:
Grizlyk said:
Simon G Best wrote: [snip]
As usual, C++ does not impose safety on the programmer; instead, it
provides tools to help the programmer write safe code.

In the case of "divide by zero" , we are speaking excactly about that
C++ does not provides tools to help the programmer write safe code. No
tools.

That is not entirely correct: you can write a wrapper template for the
arithmetic types. I confine myself to a rough outline. Also, I will
acknowledge that it is impossible to make such wrappers behave exactly
like the built in types (e.g., with regard to automatic promotions and
conversions). However, in many cases, such a wrapper can be used. E.g.,
for debugging pointers, I do use a smart pointer whose operator* method
does assert( underlying_pointer != 0 ).

#include <stdexcept>
#include <string>

struct divide_by_zero : public std::eek:ut_of_range {

divide_by_zero ( std::string const & msg )
: std::eek:ut_of_range( msg )
{}

}; // divide_by_zero

template < typename ArithmeticType >
struct throw_divide_by_zero {

typedef ArithmeticType base_type;

explicit
throw_divide_by_zero ( base_type x = 0 )
: data ( x )
{}

friend
throw_divide_by_zero operator+ ( throw_divide_by_zero lhs,
throw_divide_by_zero rhs ) {
return ( throw_divide_by_zero( lhs.data + rhs.data ) );
}

// many more friends
// ...

// and finally, division:

friend
throw_divide_by_zero operator/ ( throw_divide_by_zero lhs,
throw_divide_by_zero rhs ) {
if( rhs.data == 0 ) {
throw ( divide_by_zero( std::string( "division by 0" ) ) );
}
return ( throw_divide_by_zero( lhs.data + rhs.data ) );
}

private:

base_type data;

}; // throw_divide_by_zero

#include <iostream>

int main ( void ) {
try {
throw_divide_by_zero<int> a ( 0 );
throw_divide_by_zero<int> b ( 3 );
b / a;
}
catch ( divide_by_zero const & c ) {
std::cout << c.what() << '\n';
}
}


Best

Kai-Uwe Bux

Kai-Uwe, this is nice. However, look at all the code you have written
for something so simple. And you also expect everyone to remember how
to do such things in all cases, possibly in complex situations?

If you have a need for this kind of thing, you write it once and the you
just use it. I do that for pointers. You do not need to re-implement that
every time. That's what library solutions are like.

Another advantage is the flexibility: if I regarded division by 0 a bug, I
could use an assert instead of a throw. I could even make that a policy
passed as a template parameter. If you put a mechanism into the core
language, you have a "one-size-fits-all" approach.

I am not entering the discussion.

We shall see about that. It appears a though you might have something to
say.

I was just wondering if you thought about your extensive creativity.

There is nothing extensive about the creativity in the code sample shown
above. The problem is fairly straight forward: the built-in types of C++
make virtually no guarantees towards enforcing preconditions of operations.
Thus, if you need types that do, you have to provide them yourself. That is
where the extensible type system of C++ comes in as the tool that the
language provides to help with these issues.

Other languages are designed differently. They may provide different means
of writing safe code. It is, however, not fair to claim that C++ does not
provide tools/mechanisms to ease safe coding.


Best

Kai-Uwe Bux
 
S

Simon G Best

Zorro said:
Thanks to all comments, and in particular yours (to my blogger) I
realized I had made a slieght error because multiple compilers gave me
the same result (on Windows). My apologies. However ...
:)

I have redone the blogger, and the article to show the disadvantages of
C++ exception model, and a better way of doing things. Looking at your
comment (above) I do not see what you are really trying to say. What do
you mean by "C++ does not impose safety on the programmer". So, how
come you cannot call a private method? It is the purpose of a modern
language to help with the safety.

The programmer is not obliged to use the private: label. The programmer
could define classes as structs, and leave everything public, for
example. C++ does not impose privacy, but provides it as a tool that
the programmer can use to discourage (quite possibly unsafe) breaches of
intended encapsulation. So, actually, private: is a very good example
of how C++ provides tools for safety without imposing safety.
Now, reread the article, without prejudice. I love C++ and I have known
it from the time before it was called C++. My argument is that,
someone's weak implementation techniques were forced into standard, and
your generation simply takes that as a religion.

You still don't seem to understand C++ :-(
 
I

IR

Zorro said:
Kai-Uwe, this is nice. However, look at all the code you have
written for something so simple.


There is one thing to keep in mind IMHO: C++ was designed to be a
_portable_ _systems_ language.

Even though it is close to the metal, it has to be the least common
denominator of all architectures it is intended to run on. Platform-
specific CPU exceptions clearly are not part of this "least common
denominator".

And no, handling a platform-specific exceptions like a division by
zero (or an access violation, for instance) is _not_ simple when you
consider that C++ can be implemented on platforms ranging from a 8-bit
68xx microcontroller up to the fastest supercomputers, with the x86
lying somewhere in the middle...

No wonder such a "trivial" task can take a few dozen lines to be
addressed correctly using only _standard_ C++...


Cheers,
 
D

David W

Ian Collins said:
Overhead isn't the point at issue, differentiating between exceptions
thrown by the application and asynchronous events generated by the
operating environment is.

Overhead has some relevance here, since you could replace such events with
exceptions. I presume that the reason that division by zero, for example,
produces undefined behaviour rather than a guaranteed C++ exception is the
checking overhead that would be necessary to throw an exception. If I have a
tight loop that does nothing but divisions and my code ensures that a division
by zero will not occur, I do not want superfluous checking for zero before every
division. Zorro wants the language to be safer, but the cost of that safety is
more than programnmers would be willing to bear. Many people choose to use C or
C++ precisely because they are "close to the machine" and in most cases do no
more than the programmer asks.

DW
 
I

IR

David said:
Overhead has some relevance here, since you could replace such
events with exceptions. I presume that the reason that division by
zero, for example, produces undefined behaviour rather than a
guaranteed C++ exception is the checking overhead that would be
necessary to throw an exception.

As a side note, all platforms I have worked on use some kind of IRQ to
notify the system when such event occurs, so it has no runtime cost in
"normal" operation in such cases.

But I agree that it is different on some architectures, and that this
may be the very reason why the C++ standard considers CPU-specific
exceptions as UB.


Cheers,
 
S

Simon G Best

IR said:
>
As a side note, all platforms I have worked on use some kind of IRQ to
notify the system when such event occurs, so it has no runtime cost in
"normal" operation in such cases.

But the (operating) system then has to handle those events and can do so
in a variety of ways. How the system handles them depends on the
system. And then, to take advantage of such event handling, the
language implementation has to do so in a way that's compatible with the
system's way of doing things. And then, when it comes to the
corresponding language-level exceptions, there may well have to be the
accompanying overheads just to answer the question, "Has a system-level
exception been thrown this time?" So, even though such system-level
exceptions wouldn't be thrown most of the time, the overheads could
still be there every single time such a piece of code gets executed,
just in case a system-level exception does get thrown.

:-|
 
S

Simon G Best

Grizlyk said:
No, "divide by zero" can occure not only while you are dividing by 0,
in many cases, when CPU can not store result, that even can take more
memory, than source and divider have together.

Unsufficient memory can happends, when we are dividing very big number
to very small number ( due to CPU hardware limitations ), when divider
less than 1 (for floationg point) and 0 is just a limit of all here, as
most small number. Mathematically, to store result from 0 we need
memory of unlimited size.

Are you talking about arithmetic overflow, and the like? I'm still not
sure quite what you meant by "divide 0xffff to 0x02", but I had thought
you meant integer division.
1. "Divide by zero" in most cases is error, for result is not the same,
as expect programmer. If it does not matter to him to get the concrete
result, why is he dividing? It is better to do addition - faster and
safe operation :)

Divide-by-zero isn't always a possibility. Such errors don't always
have the possibility of occurring. Consider the following:-

int foo(int x) {
for (int i(1); i <= 10; ++i) // i can't ever be zero.
x /= i; // Can never be divide-by-zero.
return x;
}

Checking for divide-by-zero, or checking for such things as system-level
(non-C++) 'exceptions', would be needless overhead in such a function.
2. We must report ALL errors to apropriate handler. Always. It is not
"overhead".

It's needless overhead when there's no possibility of such errors occuring.
3. "Divide by zero" handled by CPU practicaly, already handled,
similarly "bad memory access". Do we throw in the cases or silently
call exit()?

No. Usually, the operating system will handle such events. How the
operating system handles them depends on the operating system itself -
and that's beyond the scope of the C++ standard, because it's outside
the C++ language. There are various ways in which an operating system
could respond to such an event. Some may just terminate the process
that caused the event - the process gets no say in the matter at all.
Some may send some sort of signal to the process - but that signal may
be asynchronous. There are other possibilities.

For a process to deal with all such events the way you want, it would
have to pre-empt such events on some systems (with overheads), check for
corresponding signals on some other systems (with overheads), and do
other stuff on yet other systems (which again may involve overheads).
Such overheads only make sense when there's a real possibility of such
events occurring, and when such events would need to be handled
properly. Having such overheads each and every time certain kinds of
operations get performed, 'just in case', would result in needless
overheads on those occasions when such operations can't possibly cause
such events anyway.

Instead, as usual, C++ lets the programmer decide whether or not such
overheads are needed, rather than imposing them on the programmer.
Lets programmer make selection. To do it, standard must allow for
programmer to make selection between "throw" or "not throw".

Indeed. That's what C++ does.
Now C++ programmer can not throw easy, No one of way below is allowed
by standard:
a) check a std:: flag _divide_error
a/=b; if(std::_divide_error)throw bad_divide();
b) do automatic throw
try{ a/=b; }catch(...){}
and programmer can not do local selection of one of them.

What C++ does is it allows the following (if I remember the relevant
syntax correctly):-

// I'm assuming int a, b;.
try {
if (b == 0) throw bad_divide();
a /= b;
} catch(...) { /*...*/ }

That's nicely platform-independent and portable :) Also, in those
cases where there's no possibility of divide-by-zero, the try-catch
stuff need not be included, as bad_divide() will never get thrown, and
there'll never be anything to catch - no needless overheads.
4. Not only "Divide by zero", all processable harware errors must be
processed as point 3. I think.

Does that include an asteroid, a Cheeky Girl, or Lembit Opik landing
abruptly on the computer?
Well,well:

try - verb, "to attempt to do any"
will try - future indefinite of try
to find - verb infinitive, "make a search of any"
will try to find - will try in order to find
a - "any kind of next noun"
cause - noun, "source of an action, root"
of - "cause of thunder" is "thunder cause"
it - you know what is "it"
and so on,

I understood the words :) but not what you meant.
Yes, and C++ exceptions is one of the way to hide the differences.

Yes, indeed :)
No, the question of safe and predictable excution is not beyond scope
of the C++, but bridge between "throw" and "CPU" of course beyond scope
of the C++.

Uh, yeah, I think :)
In the case of "divide by zero" , we are speaking excactly about that
C++ does not provides tools to help the programmer write safe code. No
tools.

The tools are there, but the programmer has to use them (as illustrated
above).
 
S

Simon G Best

Grizlyk said:
No, we can not write wrapper, because "divide by zero" error rising in
CPU, out of C++ scope.

What you do is you pre-empt such divisions by zero from even occurring
in the first place. That's the sort of thing that the suggested
wrappers do. No need to handle signals (or whatever) from lower down.
 
Z

Zorro

Simon said:
But the (operating) system then has to handle those events and can do so
in a variety of ways. How the system handles them depends on the
system. And then, to take advantage of such event handling, the
language implementation has to do so in a way that's compatible with the
system's way of doing things. And then, when it comes to the
corresponding language-level exceptions, there may well have to be the
accompanying overheads just to answer the question, "Has a system-level
exception been thrown this time?" So, even though such system-level
exceptions wouldn't be thrown most of the time, the overheads could
still be there every single time such a piece of code gets executed,
just in case a system-level exception does get thrown.

:-|

Comments starting with David, IR and here Simon are all valid and quite
interesting. The focal point is made by Simon, that the run-time lib of
language must have some form of check, for instance, is a flag set
(This can be done efficiently, and is not as bad as it sounds).
However, the model that results from having run-time support is
interesting too. As statements are executed, should an exception occur,
there is no need to write a statement such as throw for it. Note that,
I did not say system exception, or exceptions defined by user, or
whatever. Generally, of course, a user will define exceptional states
unknown to the libraries, and must explicitly raise those exceptions
(do a throw). Nevertheless, there are always an array of well-known
exceptions that the libraries can support. That is where the price is
paid. Do we want the run-time to check, or do we want the compiler to
take charge in all cases. When we leave it to the compiler
(synchronous), run-time library will generally not need to be aware of
exceptions occuring below it because the system will abort it.
Without support from run-time libs, I do not believe there is a way to
support resumption.
So, one has to accept a language for its purpose. As pointed out in
your notes, C and C++ are system programming languages, and one expects
them to be efficient.

I hope this helps, and that I have not said something against C++.
Regards.
 
Z

Zorro

Erik said:
I was only giving an answer to the question 'What do you mean by "C++
does not impose safety on the programmer"'.

My apologies for the misunderstanding on my part.
Regards.
 
Z

Zorro

Kai-Uwe Bux said:
Zorro said:
Kai-Uwe Bux said:
Grizlyk wrote:

Simon G Best wrote:
[snip]
As usual, C++ does not impose safety on the programmer; instead, it
provides tools to help the programmer write safe code.

In the case of "divide by zero" , we are speaking excactly about that
C++ does not provides tools to help the programmer write safe code. No
tools.

That is not entirely correct: you can write a wrapper template for the
arithmetic types. I confine myself to a rough outline. Also, I will
acknowledge that it is impossible to make such wrappers behave exactly
like the built in types (e.g., with regard to automatic promotions and
conversions). However, in many cases, such a wrapper can be used. E.g.,
for debugging pointers, I do use a smart pointer whose operator* method
does assert( underlying_pointer != 0 ).

#include <stdexcept>
#include <string>

struct divide_by_zero : public std::eek:ut_of_range {

divide_by_zero ( std::string const & msg )
: std::eek:ut_of_range( msg )
{}

}; // divide_by_zero

template < typename ArithmeticType >
struct throw_divide_by_zero {

typedef ArithmeticType base_type;

explicit
throw_divide_by_zero ( base_type x = 0 )
: data ( x )
{}

friend
throw_divide_by_zero operator+ ( throw_divide_by_zero lhs,
throw_divide_by_zero rhs ) {
return ( throw_divide_by_zero( lhs.data + rhs.data ) );
}

// many more friends
// ...

// and finally, division:

friend
throw_divide_by_zero operator/ ( throw_divide_by_zero lhs,
throw_divide_by_zero rhs ) {
if( rhs.data == 0 ) {
throw ( divide_by_zero( std::string( "division by 0" ) ) );
}
return ( throw_divide_by_zero( lhs.data + rhs.data ) );
}

private:

base_type data;

}; // throw_divide_by_zero

#include <iostream>

int main ( void ) {
try {
throw_divide_by_zero<int> a ( 0 );
throw_divide_by_zero<int> b ( 3 );
b / a;
}
catch ( divide_by_zero const & c ) {
std::cout << c.what() << '\n';
}
}


Best

Kai-Uwe Bux

Kai-Uwe, this is nice. However, look at all the code you have written
for something so simple. And you also expect everyone to remember how
to do such things in all cases, possibly in complex situations?

If you have a need for this kind of thing, you write it once and the you
just use it. I do that for pointers. You do not need to re-implement that
every time. That's what library solutions are like.

Another advantage is the flexibility: if I regarded division by 0 a bug, I
could use an assert instead of a throw. I could even make that a policy
passed as a template parameter. If you put a mechanism into the core
language, you have a "one-size-fits-all" approach.

I am not entering the discussion.

We shall see about that. It appears a though you might have something to
say.

I do, but I really meant what I said. This will take a long discussion.
There is nothing extensive about the creativity in the code sample shown
above. The problem is fairly straight forward: the built-in types of C++
make virtually no guarantees towards enforcing preconditions of operations.
Thus, if you need types that do, you have to provide them yourself. That is
where the extensible type system of C++ comes in as the tool that the
language provides to help with these issues.

Other languages are designed differently. They may provide different means
of writing safe code. It is, however, not fair to claim that C++ does not
provide tools/mechanisms to ease safe coding.

Perhaps more like "it can become better" as in future revisions of the
standard. In other words, the point of discussion is what else can be
done, rather than putting down C++. I doubt the current standard is end
of evolution for C++.

Regards.
 
Z

Zorro

IR said:
There is one thing to keep in mind IMHO: C++ was designed to be a
_portable_ _systems_ language.

Even though it is close to the metal, it has to be the least common
denominator of all architectures it is intended to run on. Platform-
specific CPU exceptions clearly are not part of this "least common
denominator".

And no, handling a platform-specific exceptions like a division by
zero (or an access violation, for instance) is _not_ simple when you
consider that C++ can be implemented on platforms ranging from a 8-bit
68xx microcontroller up to the fastest supercomputers, with the x86
lying somewhere in the middle...

No wonder such a "trivial" task can take a few dozen lines to be
addressed correctly using only _standard_ C++...


Cheers,

This is a very good point.
Regards.
 
Z

Zorro

Gavin said:
Yes, it does. It seems to me reading this thread that your entire
question is based on coincidence. CPU designers have the concept of
what happens in awkward situations like divide by zero, and C++
programmers have the concept of what happens in responce to a throw
statement, and while those two concepts are utterly unrelated, both
groups of people (CPU designers and C++ programmers) happen to have
chosen the word "exception" to describe their concept.

If CPU designers had instead chosen the word "FUBAR" for their concept,
and C++ programmers had chosen the word "tadpole" for theirs, do you
really think your original question would have arisen?

Gavin Deane

Gavin, the original question was the result of an error. I have
apologized for that. I further appreciate the comments that stopped me
from making a bigger fool of myself by inferring from the behavior of
C++ compilers on a single operating system. The article was redone as a
result, thanks everyone.

About the term "exception", the CPU simply sets a bit (say the overflow
bit in an addition). Depending on the context, the operating system may
or may not consider the instruction as invalid. If it does, it will
raise what has become known as exception. That was done by programmers,
who at the time had to use assembly language (I have done a lot of it),
later C and now C++.

The issue really never was "division-by-zero". It was the jump made to
the catch point without a throw, and resulting in destructors not being
called. I was wrong in that, this had nothing to do with C++ standard.

As comments were made, I replied the best way I could to end the topic.
But it has taken its own life, as pointed out by one of the authors.
For instance, I had to say something about your comment, and I am
trying to stay out of what could take a long discussion.

Regards.
 
G

Grizlyk

Gavin said:
Have you missed the point? In C++, the built in arithmetic types
provide no protection against divide-by-zero.

Yes, you aree with me, that C++ does not provide any protection against
divide-by-zero. The couse of it
1. snow in California in 2004 year,
2. mongol invasion,
3. erathquake on Mars,
or something else
it does not matter for us, because we must write low-level error
handler for unknown target machine with C++ compiler, because C++ does
not do it for us.
No? see below.
But you snipped Kai-Uwe's code which showed
a wrapper template that included the following overloaded operator:

<quote>
friend
throw_divide_by_zero operator/ ( throw_divide_by_zero lhs,
throw_divide_by_zero rhs ) {
if( rhs.data == 0 ) {
throw ( divide_by_zero( std::string( "division by 0" ) ) );
}
return ( throw_divide_by_zero( lhs.data + rhs.data ) );
}
</quote>
if( rhs.data == 0 )
I am repeating again - source of the exception is hardware (CPU), the
test (rhs.data == 0) _is not enough_.
lhs.data + rhs.data
Means ( lhs.data / rhs.data ) - do you want to divide without CPU?

In general, you need another special CPU or coprocessor in your system,
which will not generate "divide by zero" to use your wrapper.
 
G

Grizlyk

Simon G Best wrote:

And this is answer for you too:

http://groups.google.com/group/comp...8e3882/1187c67153b5cb46?#doc_13940264e5e65ae9
Are you talking about arithmetic overflow, and the like? I'm still not
sure quite what you meant by "divide 0xffff to 0x02", but I had thought
you meant integer division.

Just write any file and post it to debug as I wrote or read any book
about ix86 asm and DIV opcode. When we are dividing short numbers, C++
compiler can extend operands to ensure, the only 0 gives the error, but
resorces of the CPU (or other CPU) is not unlimited.
Divide-by-zero isn't always a possibility. Such errors don't always
have the possibility of occurring. Consider the following:-

Checking for divide-by-zero, or checking for such things as system-level
(non-C++) 'exceptions', would be needless overhead in such a function.

I can not understand metter of your objections and why "needless
overhead" will appear.
It's needless overhead when there's no possibility of such errors occuring.

I can not understand metter of your objections and why reporting about
error is "needless overhead", especially if error checking is hardware.

No. Usually, the operating system will handle such events.

No of course, OS can only protect other parts of system, from you and
your program, OS _can not_ resume your program after error. It is
evidently.

C language proposes "signals" mechanism as std library, but it is not
enough for C++ and does not garantee any except halt by error. I think.
How the
operating system handles them depends on the operating system itself -
and that's beyond the scope of the C++ standard, because it's outside
the C++ language.

"How the operating system handles" is really out of C++, but "how you
program will continue" is not, is not out of C++.
Indeed. That's what C++ does.

No, does not, because C++ had no std tools to control it.
try {
if (b == 0) throw bad_divide();

"(b == 0)" is not enough to prevent "divide by zero" error.
That's nicely platform-independent and portable :) No


Does that include an asteroid, a Cheeky Girl, or Lembit Opik landing
abruptly on the computer?

What is "Cheeky Girl" or "Lembit Opik" or "abruptly" in other places?

If result of any operation is incorrect (overflow for example), in most
cases function will work with error. But in the case of "divide by
zero" all other perfectly working parts of program will be halted. It
will be much better if C++ will garantee, that program will not be halt
automaitcally on error like overflow.
I understood the words :) but not what you meant.
This is allegory.
 
I

Ian Collins

There's nothing an application can do if the operating environment
terminates it.
"How the operating system handles" is really out of C++, but "how you
program will continue" is not, is not out of C++.


No, does not, because C++ had no std tools to control it.
You can't have standard tools to control something the application
cannot control.
 
K

Kai-Uwe Bux

Grizlyk said:
Yes, you aree with me, that C++ does not provide any protection against
divide-by-zero. The couse of it
1. snow in California in 2004 year,
2. mongol invasion,
3. erathquake on Mars,
or something else
it does not matter for us, because we must write low-level error
handler for unknown target machine with C++ compiler, because C++ does
not do it for us.
No? see below.


I am repeating again - source of the exception is hardware (CPU), the
test (rhs.data == 0) _is not enough_.

For integral types, the c++ standard guarantees that this test is enough:
division by any integer different from 0 is guaranteed to produce an
(implementation defined) result. For floating point arithmetic, life may be
harder.
Means ( lhs.data / rhs.data ) - do you want to divide without CPU?

Again, for integer types, the above line is guaranteed to work by the
standard provided rhs.data is a legal value != 0.
In general, you need another special CPU or coprocessor in your system,
which will not generate "divide by zero" to use your wrapper.

No you don't. All you need is a standard compliant compiler. Should the CPU
do something funny when you divide 10 by 3 the compiler would have to work
around this in order to be standard compliant.

I concede that (depending on the platform) there can be bit patterns that do
not represent legal values for signed integer types. You just have to write
the wrapper class so that it guarantees as an invariant that the underlying
integer data will never be invalid. This will require adding overflow
detection for +,-, and *. For unsigned integer types, all bit patterns are
valid values and these measures are unnecessary.


Best

Kai-Uwe Bux
 
G

Grizlyk

Ian said:
There's nothing an application can do if the operating environment
terminates it.

Most OS _never_ will terminate process on overflow, or any recoverable
error, but OS _never_ can resume process. C language declare "signals"
as interdace between OS and C, but signals is too simple for C++. C++
must support better processing for the errors, as C++ support
exceptions instead of return error value.

If OS will terminate process on overflow, it is concrete OS problem, as
DOS has no native threads suport, for example.
You can't have standard tools to control something the application
cannot control.

Application can control.
 
K

Kai-Uwe Bux

Kai-Uwe Bux said:
For integral types, the c++ standard guarantees that this test is enough:
division by any integer different from 0 is guaranteed to produce an
(implementation defined) result. For floating point arithmetic, life may
be harder.

Oops: as it turns out, the signed types hit me again. Depending on the
representation of signed int, it is possible that INT_MIN / -1 is not
representable as a signed int. So there is one more case that one would
need to check before dividing. I hate signed types.


Best

Kai-Uwe Bux
 

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,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top