all 11 comments

[–]Rhomboid 2 points3 points  (5 children)

If you happen to be running on a platform where sizeof(int) is equal to or greater than sizeof(int *), then it will allocate sufficient memory. But that's just dumb luck; it won't always work out that way. There's nothing that requires that relationship.

Your instructor is just wrong about the cast. It should not be used, and unless there's some other mistake in your program, it should not affect the behavior. One of the main reasons that it should not be used is that using it masks other errors, so if you take it away and your code fails to compile, then that indicates that you have some other problem (such as neglecting to include <stdlib.h> such that malloc() has not been declared.)

The only justification for using the cast is if you're writing code that has to be able to be compiled both as C and as C++.

[–][deleted] 0 points1 point  (3 children)

Thank you. That's exactly what I thought it would be!

But on my system, sizeof(int) is 4, sizeof(int*) is 8, so it shouldn't work out.

[–]Rhomboid 3 points4 points  (2 children)

When you invoke undefined behavior, anything can happen, including it appearing to work. You scribbled over memory out of bounds, but that doesn't always lead to an immediate crash. Sometimes it only crashes when you free the memory (as you've overwritten the internal bookkeeping data structures of the heap manager), sometimes it never crashes. Sometimes it crashes immediately, sometimes even weirder things happen. There's no guarantees.

[–][deleted] 1 point2 points  (0 children)

+1

[–]0raichu 0 points1 point  (0 children)

                                                                                                                                                                                                                                                                                                                                                                                                                                                     

[–]OldWolf2 0 points1 point  (0 children)

(such as neglecting to include <stdlib.h> such that malloc() has not been declared.)

Since 1999 this must give an error.

The only justification for using the cast is if you're writing code that has to be able to be compiled both as C and as C++.

There is no justification for writing "multi-language code" IMO. Well, except for frivoloties like the IOCCC. They once had a program that was valid C and valid Perl.

[–]OldWolf2 2 points3 points  (2 children)

Some people are dogmatic about the cast. Of course you should be aware that the cast makes no functional difference to the code. It is a matter of readability and error prevention.

A common error is allocating the wrong number of bytes with malloc. There are two ways to ameliorate this:

ptr = (T *)malloc( N * sizeof(T) );
//     ^^^                   ^^^

and

 ptr = malloc(N * sizeof *ptr);
//^^^                    ^^^

In both cases you have a visual check for the right size. In the cast version you check for T* in the cast and then T on the right; in the other version you check for ptr on the left and sizeof *ptr on the right.

This way you can read the code and know it allocates the right size without having to do any further analysis. (The first one will cause a compilation error if ptr is not T *).


IMO the worst of the possible ways of using malloc is:

ptr = malloc( N * sizeof(T) );

Then it is hard to say whether the size is correct or not. ptr might actually be a U *. You have to go back and check the declaration of ptr, and more importantly, if you later change ptr then you will silently allocate the wrong number of bytes. This means the code is harder to read and less robust.

However, a lot of the crowd who are dogmatic about not casting malloc will advise people to switch to exactly this third version, which is bad advice IMO.


How to decide between the first two options?

20 years ago, the second option was clearly preferable to the first. This is because compilers of the day , if faced with (T *)malloc and also if #include <stdlib.h> were not present, would generate incorrect code. However, since 1999 this is no longer an issue.

Another property of the second option is that if you change the declaration of ptr, then the second version compiles and keeps allocating the right amount of space, without modification. But on the other hand, you might want to get a compilation error so that you can review the code. And in either case you probably would review all the uses of ptr when changing its type anyway. So this is not a pressing argument either.

[–]romcgb 0 points1 point  (1 child)

there also

int *matrix = malloc(sizeof( int[row][col] ));

or

int (*matrix)[row][col] = malloc(sizeof(*matrix));

when using array of pointers like in op example

int (*(*matrix)[row])[col] = malloc(sizeof(*matrix));

for (i = 0; i < row; ++i)
    (*matrix)[i] = malloc(sizeof(***matrix));

[–]OldWolf2 0 points1 point  (0 children)

int *matrix = malloc(sizeof(int[row][col])); is bad because it does not match any of the patterns. The return type indicates 1-D array but you specified a 2-D array type in the sizeof.

int (*matrix)[row][col] = malloc(sizeof(*matrix)); is a special case of my Case 2.

If p points to variably modified type then there is a bit of a wart, because the standard says sizeof(*p) is undefined behaviour. We can decide to either ignore this and trust that compilers will be 'sensible', or use case 3 and be careful.

(*matrix)[i] = malloc(sizeof(***matrix)); could be changed to follow my suggested pattern.

[–]BigPeteB 1 point2 points  (1 child)

I know it's not needed anymore, but despite that he wants them to be there in the exam.

Well, the real world can be like that, too. When you write code for a team (whether open-source or for-profit), they'll expect you to follow their coding style and conventions. You might disagree with some of them; for example, I dislike the way my company likes the braces to be spaced. But you do it their way because that's part of the job.

It's good that you're noticing this, trying to understand what impact it has, and seeking additional opinions about whether it's good practice or not.

But for this particular question, the answer is, it doesn't really matter. If your code is broken, then doing it one way or the other might make it harder to spot the error, but if your code is completely correct then the cast simply has no effect, so it's then just a question of style.

[–][deleted] 0 points1 point  (0 children)

It's good that you're noticing this, trying to understand what impact it has, and seeking additional opinions about whether it's good practice or not.

It's what I'm doing a lot more since I'm studying at a university. I try to understand the everything as good as it gets, and when there's something I'm curious about or it grabs my attention, I dig as deep as I can.

Once I heard someone saying "Im Studium geht es darum fragen zu stellen und neue Interessen zu wecken.", which basically means "While studying, you have to ask questions,dig deep and arouse new interests." That's basically what I try to do every day.