int** to void**

J

John

Hi All,

I wrote a function in C and compiled in gcc, I got a warning such as:

.../test/test.c:26: warning: passing arg 1 of `ORCdarrayfree' from
incompatib
le pointer type

Then I goto 26th line, I found the function ORCdarrayfree have a
parameter with type void**,
but I passed int** to it.

So anyone can explain it for me and help me avoid this warning?

Thanks!
 
S

Seebs

So anyone can explain it for me and help me avoid this warning?

Quite simply, you can't do that.

You can convert void* to int*, but not void** to int**.

-s
 
K

Keith Thompson

Ralph Malph said:
int** i;
void** v=(void**) i;

[top-posting corrected]

Using a cast to silence a warning is almost never the right thing
to do.

void* is a generic pointer type, in the sense that you can convert
from any pointer type (excluding pointer-to-function types) to void*
and back again without loss of information.

There is no generic pointer-to-pointer type.

I don't know what ORCdarrayfree, but apparently it requires an
argument of type void**, most likely pointing to the first element
of an array of void*. The int** argument you're passing is probably
a pointer to the first element of an array of int*. void* and int*
are distinct and incompatible types.

You need to pass a valid void** value to ORCdarrayfree. The question
is how you can obtain such a value. We can't guess at an answer
without knowing more about ORCdarrayfree and what you're trying to
pass to it.
 
K

Keith Thompson

Seebs said:
Quite simply, you can't do that.

You can convert void* to int*, but not void** to int**.

Correction: You *can* convert from void** to int** (or, in this case,
from int** to void**) by using a cast, but you almost certainly
shouldn't.
 
D

Donkey Hottie

Correction: You *can* convert from void** to int** (or, in this case,
from int** to void**) by using a cast, but you almost certainly
shouldn't.

Why not? If pointer arithmetic is not wanted, what do we miss? A pointer
is pointer no matter what.
 
K

Keith Thompson

Donkey Hottie said:
Why not? If pointer arithmetic is not wanted, what do we miss? A
pointer is pointer no matter what.

void* and int* are incompatible types.

Consider an implementation where void* is 64 bits and int* is 32
bits (such an implementation is certainly permitted, and there could
be valid reasons for it). Then the function expects a void** that
points to the first element of an array of void* objects, each of
which is 64 bits -- but you're passing it an int** that points to the
first element of an array of int* objects, each of which is 32 bits.

Conversions from one pointer type to another can be safe in certain
cases. Interpreting a stored object of one pointer type as if it
were of a different type is not safe.

And even if, as is the case on many implementations, all pointers
happen to have the same representation, treating different pointer
types as if they were interchangeable tends to be logically
incorrect, even if you can get away with it.

The ORCdarrayfree function that the OP is trying to call expects
a void** argument. Presumably there's a good reason for that.
Or perhaps ORCdarrayfree itself is poorly designed. We don't
have enough information to know what the right solution is,
but we can be reasonably sure that passing an int** to something
expecting a void** isn't it.
 
J

John

int** i;
void** v=(void**) i;

[top-posting corrected]

Using a cast to silence a warning is almost never the right thing
to do.

void* is a generic pointer type, in the sense that you can convert
from any pointer type (excluding pointer-to-function types) to void*
and back again without loss of information.

There is no generic pointer-to-pointer type.

I don't know what ORCdarrayfree, but apparently it requires an
argument of type void**, most likely pointing to the first element
of an array of void*.  The int** argument you're passing is probably
a pointer to the first element of an array of int*.  void* and int*
are distinct and incompatible types.

You need to pass a valid void** value to ORCdarrayfree.  The question
is how you can obtain such a value.  We can't guess at an answer
without knowing more about ORCdarrayfree and what you're trying to
pass to it.

ORCdarrayfree is used to free 2-d array allocated by malloc.
The src is:

/* ORCdarraynew function return void** ,
and ORCdarrayfree should get void** to
make these 2 functions to be used in
allocating integer, char, and other
customize type */

void**
ORCdarraynew (int row, int col, int size)
{
void **arr;

arr = (void **) malloc (sizeof(void *) * row + size * row * col);
if ( arr != NULL ) {
void *head;

head = (void *) arr + sizeof(void *) * row;
memset (arr, 0, sizeof(void *) * row + size * row * col);
while (row--) {
arr[row] = head + size * row * col;
}
}

return arr;
} /* End of ORCdarraynew*/

void
ORCdarrayfree (void **arr)
{
if ( arr != NULL ) {
free (arr);
arr = NULL;
}
} /* End of ORCdarrayfree*/


We can call them like:

int **p;
p = (int**)ORCdarraynew(nrows, ncols, sizeof(int));

ORCdarrayfree(p);

So the parameter of ORCdarrayfree will be converted from int** to
void**.
While, the gcc compiler give me a warning:

warning: passing arg 1 of `ORCdarrayfree' from
incompatib
le pointer type

So please help me.
 
K

Keith Thompson

John said:
ORCdarrayfree is used to free 2-d array allocated by malloc.
The src is:

/* ORCdarraynew function return void** ,
and ORCdarrayfree should get void** to
make these 2 functions to be used in
allocating integer, char, and other
customize type */

void**
ORCdarraynew (int row, int col, int size)

These should probably be size_t rather than int.
{
void **arr;

arr = (void **) malloc (sizeof(void *) * row + size * row * col);

Casting the result of malloc is unnecessary and can mask errors.
if ( arr != NULL ) {
void *head;

head = (void *) arr + sizeof(void *) * row;
memset (arr, 0, sizeof(void *) * row + size * row * col);
while (row--) {
arr[row] = head + size * row * col;
}
}

return arr;
} /* End of ORCdarraynew*/

So you're allocating a chunk of memory consisting of an array of void*
pointers, immediately followed by a 2-dimensional array of some
arbitrary data, where each of the initial void* pointers points to a
row in the 2d array. Interesting. Let's call these two sections the
"index" and the "table", respectively.

You're making some non-portable assumptions, though.

The memset call zeros both the index and the table. The former is
useless, since you immediately initialize the table anyway. Zeroing
the table may or may not be sensiple, depending on what data you're
storing. Remember that null pointers aren't necessarily all-bits-zero
(though they commonly are).

You're also assuming that the table will be properly aligned.
Consider a system where void* is 32-bit aligned, double is 64-bit
aligned, and you want to allocate a table of doubles with an odd
number of rows.
void
ORCdarrayfree (void **arr)
{
if ( arr != NULL ) {
free (arr);
arr = NULL;
}
} /* End of ORCdarrayfree*/

Note that the "arr != NULL" test is unnecessary; free() does nothing
if you give it a null pointer argument. And setting arr to NULL is
useless; you're just clobbering a local object. You might want to do
"*arr = NULL;" -- or you might just want to leave it alone.
We can call them like:

int **p;
p = (int**)ORCdarraynew(nrows, ncols, sizeof(int));

ORCdarrayfree(p);

So the parameter of ORCdarrayfree will be converted from int** to
void**.
While, the gcc compiler give me a warning:

warning: passing arg 1 of `ORCdarrayfree' from
incompatib
le pointer type

gcc is correct; the pointer types are incompatible.
So please help me.

I think you need to allocate the index and the table in two separate
calls to malloc().

Here's a thought. Allocate row+1 void* elements for the index.
Allocate size*row*col bytes for the table; using a separate call to
malloc guarantees proper alignment. Store the address of the table in
the 0th element of the index, then return the address of the 1st
element. ORCdarrayfree can then index backwards to obtain the address
of the table, free the table, then free the index.

I haven't completely thought this through. This approach should be
type-safe, but I think you might have some problems with indexing
(e.g., p[x][y]).
 
K

Keith Thompson

pete said:
John wrote: [...]
/* ORCdarraynew function return void** ,
and ORCdarrayfree should get void** to
make these 2 functions to be used in
allocating integer, char, and other
customize type */

void**
ORCdarraynew (int row, int col, int size)
{ [...]
We can call them like:

int **p;
p = (int**)ORCdarraynew(nrows, ncols, sizeof(int));

ORCdarrayfree(p);
[...]
Why don't you rewrite ORCdarraynew to return type (int**) instead?

Because it's intended to allocate a 2d array of any arbitrary type;
that's what the "size" parameter is for.
 
J

John

These should probably be size_t rather than int.
Why?



Casting the result of malloc is unnecessary and can mask errors.

In C, I think so. Thanks.
   if ( arr != NULL ) {
      void *head;
      head = (void *) arr + sizeof(void *) * row;
      memset (arr, 0, sizeof(void *) * row + size * row * col);
      while (row--) {
         arr[row] = head + size * row * col;
      }
   }
   return arr;
} /* End of ORCdarraynew*/

So you're allocating a chunk of memory consisting of an array of void*
pointers, immediately followed by a 2-dimensional array of some
arbitrary data, where each of the initial void* pointers points to a
row in the 2d array.  Interesting.  Let's call these two sections the
"index" and the "table", respectively.

You're making some non-portable assumptions, though.

The memset call zeros both the index and the table.  The former is
useless, since you immediately initialize the table anyway.  Zeroing
the table may or may not be sensiple, depending on what data you're
storing.  Remember that null pointers aren't necessarily all-bits-zero
(though they commonly are).

memset is used to init the table and index, why is it wrong?
You're also assuming that the table will be properly aligned.
Consider a system where void* is 32-bit aligned, double is 64-bit
aligned, and you want to allocate a table of doubles with an odd
number of rows.

I think double* and double** and void* and void** have same length.
then what is your concern?
Note that the "arr != NULL" test is unnecessary; free() does nothing
if you give it a null pointer argument.  And setting arr to NULL is
useless; you're just clobbering a local object.  You might want to do
"*arr = NULL;" -- or you might just want to leave it alone.

free(arr) can free all space by malloc, but don't set arr to NULL.
I add set arr to NULL is to make the statement after ORCdarrayfree
can not use arr.

And also, I don't think I need to set *arr to NULL if I set arr to
NULL
after ORCnarrayfree.
We can call them like:
int **p;
p = (int**)ORCdarraynew(nrows, ncols, sizeof(int));

So the parameter of ORCdarrayfree will be converted from int** to
void**.
While, the gcc compiler give me a warning:
warning: passing arg 1 of `ORCdarrayfree' from
incompatib
le pointer type

gcc is correct; the pointer types are incompatible.
So please help me.

I think you need to allocate the index and the table in two separate
calls to malloc().

Here's a thought.  Allocate row+1 void* elements for the index.
Allocate size*row*col bytes for the table; using a separate call to
malloc guarantees proper alignment.  Store the address of the table in
the 0th element of the index, then return the address of the 1st
element.  ORCdarrayfree can then index backwards to obtain the address
of the table, free the table, then free the index.

I haven't completely thought this through.  This approach should be
type-safe, but I think you might have some problems with indexing
(e.g., p[x][y]).

Bingo, cannot use p[x][y] is the reason why I don't use your way.
 
S

Seebs


Because they're sizes, and sizes should usually be represented in
size_t. For instance, on some systems, you might find that size_t
can represent values up to 2 billion, while int can only handle values
up to 32,767.
memset is used to init the table and index, why is it wrong?

Because using memset to "zero" a pointer is not guaranteed to produce
null pointers.
I think double* and double** and void* and void** have same length.
then what is your concern?

You missed the key case: Are "void *" and "double" (no *) the same length?
Not on some fairly common systems. What this means is that since you
start the doubles immediately after the index, if there's an odd number
of rows, you're starting to store 8-byte-aligned values on a 4-byte-aligned
address. That could fail.
And also, I don't think I need to set *arr to NULL if I set arr to
NULL
after ORCnarrayfree.

Except that something else might still have a pointer to *arr, so you
want to make sure the thing pointed to has been zeroed out.
I haven't completely thought this through.  This approach should be
type-safe, but I think you might have some problems with indexing
(e.g., p[x][y]).
Bingo, cannot use p[x][y] is the reason why I don't use your way.

There are ways to make that work which are more consistent with respecting
the spirit of C.

Here's the thing you're missing: It is better to write things according
to the formal specification of the language than according to what you think
you know about the specific machine you are using. It is more reliable,
and more likely to work even if you are wrong about the machine, or if the
machine gets changed suddenly.

There are programs out there that, on a few days' notice, had to convert
from 32-bit big-endian systems to 64-bit little-endian systems. If they were
written portably, that was no trouble at all...

-s
 
J

John

pete said:
Keith Thompson wrote:
void*
ORCdarraynew(size_t row, size_t col, size_t size)
{
   char **arr;
   arr = malloc(row * sizeof *arr);
   if (arr != NULL) {
      while (row-- != 0) {
         arr[row] = malloc(col * size);
         if (arr[row] == NULL) {
             ORCdarrayfree(arr, row);
             arr = NULL;
             break;
         }
      }
   }
   return arr;
}

The loop should count up, instead of down, because of
the way that ORCdarrayfree is called in case of malloc failure.

void*
ORCdarraynew(size_t row, size_t col, size_t size)
{
    char **arr;
    size_t index;

    arr = malloc(row * sizeof *arr);
    if (arr != NULL) {

       for (index = 0; index != row; ++index) {
          arr[row] = malloc(col * size);
          if (arr[index] == NULL) {
              ORCdarrayfree(arr, index);
              arr = NULL;
              break;
          }
       }
    }
    return arr;

}

This kind new function can not allocate continues memory for 2-d
array, do you think so?

John Cui
 
J

John

pete said:
Keith Thompson wrote:
void*
ORCdarraynew(size_t row, size_t col, size_t size)
{
   char **arr;
   arr = malloc(row * sizeof *arr);
   if (arr != NULL) {
      while (row-- != 0) {
         arr[row] = malloc(col * size);
         if (arr[row] == NULL) {
             ORCdarrayfree(arr, row);
             arr = NULL;
             break;
         }
      }
   }
   return arr;
}

The loop should count up, instead of down, because of
the way that ORCdarrayfree is called in case of malloc failure.

void*
ORCdarraynew(size_t row, size_t col, size_t size)
{
    char **arr;
    size_t index;

    arr = malloc(row * sizeof *arr);
    if (arr != NULL) {

       for (index = 0; index != row; ++index) {
          arr[row] = malloc(col * size);
          if (arr[index] == NULL) {
              ORCdarrayfree(arr, index);
              arr = NULL;
              break;
          }
       }
    }
    return arr;

}

This kind new function can not allocate continues memory for 2-d
array, do you think so?

John Cui
 
J

John

John said:
pete wrote:
Keith Thompson wrote:
void*
ORCdarraynew(size_t row, size_t col, size_t size)
{
  char **arr;
  arr = malloc(row * sizeof *arr);
  if (arr != NULL) {
     while (row-- != 0) {
        arr[row] = malloc(col * size);
        if (arr[row] == NULL) {
            ORCdarrayfree(arr, row);
            arr = NULL;
            break;
        }
     }
  }
  return arr;
}
The loop should count up, instead of down, because of
the way that ORCdarrayfree is called in case of malloc failure.
void*
ORCdarraynew(size_t row, size_t col, size_t size)
{
   char **arr;
   size_t index;
   arr = malloc(row * sizeof *arr);
   if (arr != NULL) {
      for (index = 0; index != row; ++index) {
         arr[row] = malloc(col * size);
         if (arr[index] == NULL) {
             ORCdarrayfree(arr, index);
             arr = NULL;
             break;
         }
      }
   }
   return arr;
}
This kind new function can not allocate continues memory for 2-d
array, do you think so?

No, but you can't do p[x][y] for arbitrary x and y,
with int **p;
unless p is pointing to a pointer.

arr = malloc(row * sizeof *arr); in your code
allocate a space,

arr[row] = malloc(col * size); in your code
allocate another space.

So I think these 2 spaces are not continues.

John Cui
 
J

John

Because they're sizes, and sizes should usually be represented in
size_t.  For instance, on some systems, you might find that size_t
can represent values up to 2 billion, while int can only handle values
up to 32,767.
Thanks!


Because using memset to "zero" a pointer is not guaranteed to produce
null pointers.

OK, then how to init the index pointers and table elements in the 2-d
array?
You missed the key case:  Are "void *" and "double" (no *) the same length?
Not on some fairly common systems.  What this means is that since you
start the doubles immediately after the index, if there's an odd number
of rows, you're starting to store 8-byte-aligned values on a 4-byte-aligned
address.  That could fail.

OK, I used viod* for supporting multiple data types, then how to
handle both
multiple data types and align issue?
Except that something else might still have a pointer to *arr, so you
want to make sure the thing pointed to has been zeroed out.

hmmm, you are more safer.
I haven't completely thought this through.  This approach should be
type-safe, but I think you might have some problems with indexing
(e.g., p[x][y]).
Bingo, cannot use p[x][y] is the reason why I don't use your way.

There are ways to make that work which are more consistent with respecting
the spirit of C.

Here's the thing you're missing:  It is better to write things according
to the formal specification of the language than according to what you think
you know about the specific machine you are using.  It is more reliable,
and more likely to work even if you are wrong about the machine, or if the
machine gets changed suddenly.

Anyway, the spirit of C is don't use p[][] kind?
and in my way, I can allocate continues memory for 2-d array. I think
it is better.

John Cui
 
S

Seebs

OK, then how to init the index pointers and table elements in the 2-d
array?

To zero pointers and floating point values:

for (i = 0; i < n; ++i)
a = 0;
OK, I used viod* for supporting multiple data types, then how to
handle both
multiple data types and align issue?

Usually, you do it by allocating two tables; one table for your pointers,
and another table for the data values they point to. If you do this right,
you can do p[x][y], and you can still free both values. (Because p[0]
will be the address of the whole data block you allocated, and p will
be the address of the index block.)
Anyway, the spirit of C is don't use p[][] kind?
No.

and in my way, I can allocate continues memory for 2-d array. I think
it is better.

You still don't get it.

You can allocate continuous memory for a 2D array separately from the
table for it, and then p[x][y] works. (Only it's not really a 2D
array; it's a 1D array of pointers to data. But it works like a 2D array.)

int *rows;
int *data;
data = malloc(rows * columns * sizeof(int));
rows = malloc(rows * sizeof(int *);
for (i = 0; i < rows; ++i) {
rows = data + (columns * i);
for (j = 0; 0 < columns; ++j)
rows[j] = 0;
}

Poof! rows[0][0] is the first integer in the data array, rows[3][4] is
the 5th item in the 4th row of the array, and so on. To free it:

free(rows[0]);
free(rows);

Two allocations, but the array contents are continuous.

-s
 
J

John

OK, then how to init the index pointers and table elements in the 2-d
array?

To zero pointers and floating point values:

        for (i = 0; i < n; ++i)
                a = 0;


The reason you don't agree with memset is memset set the elements to
integer?
++
memset
Syntax:
#include <string.h>
void *memset( void *buffer, int ch, size_t count );

The function memset() copies ch into the first count characters of
buffer, and returns buffer. memset() is useful for intializing a
section of memory to some value. For example, this command:
++
OK, I used viod* for supporting multiple data types, then how to
handle both
multiple data types and align issue?

Usually, you do it by allocating two tables; one table for your pointers,
and another table for the data values they point to.  If you do this right,
you can do p[x][y], and you can still free both values.  (Because p[0]
will be the address of the whole data block you allocated, and p will
be the address of the index block.)
Anyway, the spirit of C is don't use p[][] kind?
No.

and in my way, I can allocate continues memory for 2-d array. I think
it is better.

You still don't get it.

You can allocate continuous memory for a 2D array separately from the
table for it, and then p[x][y] works.  (Only it's not really a 2D
array; it's a 1D array of pointers to data.  But it works like a 2D array.)

        int *rows;
        int *data;
        data = malloc(rows * columns * sizeof(int));
        rows = malloc(rows * sizeof(int *);

2 malloc functions nearby means the space is continus?
        for (i = 0; i < rows; ++i) {
                 rows = data + (columns * i);
                 for (j = 0; 0 < columns; ++j)
                        rows[j] = 0;
        }

Poof!  rows[0][0] is the first integer in the data array, rows[3][4] is
the 5th item in the 4th row of the array, and so on.  To free it:

        free(rows[0]);
        free(rows);

Two allocations, but the array contents are continuous.


You code don't solve the multiple data type issue either, I think.

John Cui
 
K

Keith Thompson

John said:

Because size_t is the appropriate type to represent sizes. Type int
could be a short as 16 bits, even on a 32-bit system.

[...]
   if ( arr != NULL ) {
      void *head;
      head = (void *) arr + sizeof(void *) * row;
      memset (arr, 0, sizeof(void *) * row + size * row * col);
      while (row--) {
         arr[row] = head + size * row * col;
      }
   }
   return arr;
} /* End of ORCdarraynew*/

So you're allocating a chunk of memory consisting of an array of void*
pointers, immediately followed by a 2-dimensional array of some
arbitrary data, where each of the initial void* pointers points to a
row in the 2d array.  Interesting.  Let's call these two sections the
"index" and the "table", respectively.

You're making some non-portable assumptions, though.

The memset call zeros both the index and the table.  The former is
useless, since you immediately initialize the table anyway.  Zeroing
the table may or may not be sensiple, depending on what data you're
storing.  Remember that null pointers aren't necessarily all-bits-zero
(though they commonly are).

memset is used to init the table and index, why is it wrong?

As I said, using memset to initialize the index is superfluous, since
you immediately assign values to every element of the index anyway,
overwriting all the zeros. As for the table, it can hold any
arbitrary type. For integer types, memset will set the elements to zero.
For other types, it may or may not.

You might consider just leaving the table uninitialized. Typically
the code that calls ORCdarraynew is going to assign values to it
anyway.

Or you can use memset to zero it, but keep in mind that, though this
will give you consisten results on a given system, those results
aren't necessarily meaningful.
I think double* and double** and void* and void** have same length.
then what is your concern?

Why do you think they have the same length? They very likely do on
your system, but that's a non-portable assumption.

Now you're free to write non-portable code if you want to. But
personally, I find that portable code tends to be cleaner, even if
it's never actually going to be ported.
free(arr) can free all space by malloc, but don't set arr to NULL.
I add set arr to NULL is to make the statement after ORCdarrayfree
can not use arr.

arr is a parameter to ORCdarrayfree. It's a local object that ceases
to exist immediately after you set it to NULL. It's a *copy* of the
argument passed by the caller. Modifying it has no effect on the
caller.

[...]
 
J

John

John said:
[...]
You need to pass a valid void** value to ORCdarrayfree.  The question
is how you can obtain such a value.  We can't guess at an answer
without knowing more about ORCdarrayfree and what you're trying to
pass to it.
ORCdarrayfree is used to free 2-d array allocated by malloc.
The src is:
/* ORCdarraynew function return void** ,
   and ORCdarrayfree should get void** to
   make these 2 functions to be used in
   allocating integer, char, and other
   customize type */
void**
ORCdarraynew (int row, int col, int size)
These should probably be size_t rather than int.

Because size_t is the appropriate type to represent sizes.  Type int
could be a short as 16 bits, even on a 32-bit system.

I agree now, thanks.
[...]


   if ( arr != NULL ) {
      void *head;
      head = (void *) arr + sizeof(void *) * row;
      memset (arr, 0, sizeof(void *) * row + size * row * col);
      while (row--) {
         arr[row] = head + size * row * col;
      }
   }
   return arr;
} /* End of ORCdarraynew*/
So you're allocating a chunk of memory consisting of an array of void*
pointers, immediately followed by a 2-dimensional array of some
arbitrary data, where each of the initial void* pointers points to a
row in the 2d array.  Interesting.  Let's call these two sections the
"index" and the "table", respectively.
You're making some non-portable assumptions, though.
The memset call zeros both the index and the table.  The former is
useless, since you immediately initialize the table anyway.  Zeroing
the table may or may not be sensiple, depending on what data you're
storing.  Remember that null pointers aren't necessarily all-bits-zero
(though they commonly are).
memset is used to init the table and index, why is it wrong?

As I said, using memset to initialize the index is superfluous, since
you immediately assign values to every element of the index anyway,
overwriting all the zeros.  As for the table, it can hold any
arbitrary type.  For integer types, memset will set the elements to zero.
For other types, it may or may not.

You might consider just leaving the table uninitialized.  Typically
the code that calls ORCdarraynew is going to assign values to it
anyway.

Or you can use memset to zero it, but keep in mind that, though this
will give you consisten results on a given system, those results
aren't necessarily meaningful.

I think the concern here is assign integer 0 to pointer or double in C
to
init, Is it wrong?
Why do you think they have the same length?  They very likely do on
your system, but that's a non-portable assumption.

Now you're free to write non-portable code if you want to. But
personally, I find that portable code tends to be cleaner, even if
it's never actually going to be ported.

I think in any platform, the pointer have the same length, do you
think
so?
arr is a parameter to ORCdarrayfree.  It's a local object that ceases
to exist immediately after you set it to NULL.  It's a *copy* of the
argument passed by the caller.  Modifying it has no effect on the
caller.

Hmm, yes, arr is a local variable, just free(arr) is enough. thanks.


John Cui
 

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,763
Messages
2,569,562
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top