In the original K&R definition of the C language (pre-1989), the *alloc
functions returned char *
. Because the language didn't (and still doesn't) allow assignments between incompatible pointer types, you had to explicitly cast the result to the target type if that type was not char *
:
int *p;
...
p = (int *) malloc( n * sizeof (int) );
The 1989 standard introduced the void *
type, which is an exception to the assignment rule above and serves as a "generic" pointer type - a value of void *
may be assigned to any pointer type without need of an explicit cast, so as of the C89 standard,
p = malloc( n * sizeof (int) );
will work.
Note that this is not true in C++ - in that language, a void *
value must be cast to the target type, so in C++ you would still have to write
p = (int *) malloc( n * sizeof (int) );
but if you're writing C++, you shouldn't be using malloc
anyway.
Among most of us, it's considered a mistake to cast the result of malloc
. Under the C89 version of the language, you could actually introduce a bug by
doing so. Under that version, if the compiler saw a function call without a declaration in scope, it would assume the function returned int
. So, if you somehow forgot to include stdlib.h
and wrote
int *p = (int *) malloc( n * sizeof (int) );
the compiler would assume malloc
returned an int
. Without the cast, this would result in an "incompatible types in assignment" diagnostic; however, including the cast would suppress the diagnostic, and you wouldn't realize there was an issue until you got a (sometimes very subtle and hard to diagnose) runtime problem.
That's no longer the case - the C99 revision of the language did away with implicit int
declarations, so you'd get a diagnostic if you forget to include stdlib.h
.
However, many of us feel that the cast adds an unnecessary maintenance burden (if you change the type of your target pointer, you have to update the cast as well), and IMO it makes code harder to read.
Why does the practice still persist in C? Several reasons:
- The author started writing C code in the late 1970s/early 1980s and just never got out of the habit of using explicit casts;
- The author learned from out-of-date references or by reading very old code;
- The author wants to explicitly document the types involved.
Personally, I think the right way to write a *alloc
call is
T *p = malloc( n * sizeof *p );
T *p = calloc( n, sizeof *p );
T *tmp = realloc( p, n * sizeof *p );
Clean, and if you ever change the type of p
, you don't need to change anything about the *alloc
call itself.
EDIT
Also I'm not familiar with what the double '*' is (**).
Multiple indirection is possible in C - you can have pointers to pointers, pointers to pointers to pointers, etc. For example, your pvowels
object points to the first in a sequence of pointers to char
:
char ** char * char
------- ------- ----
+---+ +---+ +---+
pvowels: | | -----> pvowels[0]: | | ------> pvowels[0][0]: | |
+---+ +---+ +---+
pvowels[1]: | | ----+ pvowels[0][1]: | |
+---+ | +---+
... | ...
+---+ | +---+
pvowels[r-1]: | | --+ | pvowels[0][c-1]: | |
+---+ | | +---+
| |
| | +---+
| +-> pvowels[1][0]: | |
| +---+
| pvowels[1][1]: | |
| +---+
| ...
| +---+
| pvowels[1][c-1]: | |
| +---+
...
Since pvowels
points to the first of a sequence of pointers to char
, its type must be "pointer to pointer to char
", or char **
.
When you *alloc
memory for a sequence of objects of type T
, the target pointer must have type T *
:
T *p = malloc( n * sizeof *p ); // allocating a sequence of T
In the statement above, the expression *p
has type T
, so sizeof *p
is equivalent to sizeof (T)
.
If we replace T
with a pointer type P *
, it looks like
P **p = malloc( n * sizeof *p ); // allocating a sequence of P *
Instead of allocating space for a sequence of P
, we're allocating space for a sequence of pointers to P
. Since p
has type P **
, the expression *p
has type P *
, so sizeof *p
is the same as sizeof (P *)
.
Note that in the pvowels
case above, what you have is not a 2D array of char
; pvowels
points to the first of a sequence of pointers, each of which points to the first of a sequence of char
. You can index it as though it were a 2D array, but it's not structured like a 2D array; the "rows" are not adjacent in memory.