1

I'm following a tutorial at: http://www.learn-c.org/en/Arrays_and_Pointers

In the code they have:

// Allocate memory for nrows pointers
char **pvowels = (char **) malloc(nrows * sizeof(char));

// For each row, allocate memory for ncols elements
pvowels[0] = (char *) malloc(ncols * sizeof(char));
pvowels[1] = (char *) malloc(ncols * sizeof(char));

but if I remove the (char *) and (char **) it still works:

// Allocate memory for nrows pointers
char **pvowels = malloc(nrows * sizeof(char));

// For each row, allocate memory for ncols elements
pvowels[0] = malloc(ncols * sizeof(char));
pvowels[1] = malloc(ncols * sizeof(char));

The tutorial doesn't explain what this is, I'm assuming it is some sort of type casting, and maybe for this example it is not important to actually type cast it to work, rather the author thought it important to explicitly type cast.

Also I'm not familiar with what the double '*' is (**).

Just want to be sure I'm not missing anything and thank you for any explanation.

Discoveringmypath
  • 1,049
  • 9
  • 23
  • Get a better tutorial. Better: get a good C book; onlione tutorial are often bugged to the ceiling. – too honest for this site May 01 '17 at 19:03
  • `char **` means a pointer to a pointer. See this question: http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc – Chris May 01 '17 at 19:03
  • 1
    The cast was needed for some C pre-1989. It is not needed now. Consider it optional, but unnecessary. – chux - Reinstate Monica May 01 '17 at 19:06
  • 3
    With or without the cast, `char **pvowels = malloc(nrows * sizeof(char));` is wrong... should be `sizeof(char *)` – Dmitri May 01 '17 at 19:07
  • 1
    Neither cast is required. There is NO need to cast the return of `malloc`, it is unnecessary. See: [**Do I cast the result of malloc?**](http://stackoverflow.com/q/605845/995714) for thorough explanation. – David C. Rankin May 01 '17 at 19:18
  • Chux, Chris, and David, thanks for the tips. @Dmitri, an explanation of "why" is more important than that wrong/this right in my opinion... – Discoveringmypath May 01 '17 at 19:55
  • @Discoveringmypath I thought the "why" would be obvious once the mistake was pointed out... you're allocating space for a bunch of `char`s, but what you *need* is space for a bunch of `char *`s (which are bigger). So you need to fix the type in the `sizeof()` so that you calculate the right size for `malloc()` -- `nrows * sizeof(char)` is less than `nrows * sizeof(char *)`, and not enough space for `nrows` `char *`s. Just for the line assigning to `pvowels`... the others really are allocating space for a bunch of `char`s, so they're fine. – Dmitri May 01 '17 at 22:32
  • Okay that makes sense, lack of experience with C. Thanks for the details! – Discoveringmypath May 01 '17 at 23:11

4 Answers4

2

The malloc function in your source code is allocating the amount of memory needed for the specific data type for your purposes (a char variable), the return used to have to be typecasted but not anymore, it's still a typical precaution.

a char * is a pointer to a variable of type char. a char ** is a pointer to a pointer to a variable of type char.

I don't know how much about pointers has been explained to you, but you can think of a char pointer as the first char being referenced in a list of char variables. That's how strings are set up in memory, and a string is just a collection of char variables.

If you go a level deeper, you can have a char pointer pointer, which points to the first string being referenced in a list of strings. That is what the author is doing here, he is allocating the memory needed for an array of char pointers, and since each char pointer in this array itself points to a set of char values, they must each have memory allocated for them as well.

EDIT: To be more specific, these "lists" of things are called Arrays. A char array can be thought of as a string, and an array of char arrays can be thought of as a sentence.

RH6
  • 144
  • 8
  • The type has nothing to do with the amount of memory. – Tyzoid May 01 '17 at 19:12
  • @Tyzoid It absolutely does. https://www.tutorialspoint.com/cprogramming/c_data_types.htm the data type is directly related to the amount of memory needed to store it in C – RH6 May 01 '17 at 19:14
  • @RH6 the amount of memory *needed* might depend on data type, but the amount `malloc()` allocates has nothing to do with the type you cast its return value to, or the type of the pointer you assign it to. – Dmitri May 01 '17 at 19:23
  • @Dmitri I was speaking on what malloc was doing in the context of the OPs source code, not what malloc itself does, edited my answer a little for clarity. – RH6 May 01 '17 at 19:25
  • It's just that the cast (in the OP's code or historically) didn't affect the amount of memory allocated. It's just a matter of whether `void *` can be implicitly converted to another pointer type. – Dmitri May 01 '17 at 19:27
  • @Dmitri I understand, I've edited my answer to be a little more clear. – RH6 May 01 '17 at 19:29
  • Thanks for the answer! Very helpful and what I was looking for. – Discoveringmypath May 01 '17 at 20:07
2

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.

John Bode
  • 119,563
  • 19
  • 122
  • 198
0

malloc returns a void * pointer, which in newer versions of C can be assigned to any pointer type without explicit casting. This wasn't always the case, and is a historical artifact that has been often left in.

Tyzoid
  • 1,072
  • 13
  • 31
  • You mean "newer" to mean any standard compliant compiler released in the last 28 years? – David C. Rankin May 01 '17 at 19:19
  • @Rankin many people still compile c99, and some still on c89; this is especially true in older legacy codebases. – Tyzoid May 01 '17 at 19:45
  • @Tyzoid If you subtract 28 from 2017, you get 1989 (e.g. C89) where the return of `malloc` was `void *` and where no cast was required to/from any other pointer type. **C89 Sect. 3.2.2.3 Pointers** *"A pointer to void may be converted to or from a pointer to any incomplete or object type."* – David C. Rankin May 01 '17 at 21:10
  • I apologize, that was the draft section, the Standard Section is **6.2.2.3** – David C. Rankin May 01 '17 at 21:27
0

pvowels is of type char ** (a pointer to a character pointer(char* )). hence malloc(which returns void*) has to be casted to the "type" of pvowels.

If u remove char* in the sizeof function, it works cus that is what you want(that malloc reserves memory nrows the size of char type) that will probably hold the address of the char pointers.

if u remove the cast char **, my compiler throws an error, just as i'd expect(compiling in cpp). In c, it works just fine.

ytobi
  • 535
  • 1
  • 9
  • 19
  • If removing the cast causes your compiler to throw an error, you're probably compiling as C++. C doesn't require explicit casting between `void *` and other pointer types, but C++ does. – Dmitri May 01 '17 at 22:23
  • my bad. was coding in cpp when i saw question. – ytobi May 02 '17 at 21:39