2

Second edition. I'm looking at their hash table example in section 6.6. I found the full source transcribed here. This is the part I'm puzzling over:

struct nlist *np;

if((np=lookup(name))==NULL){
    np=(struct nlist *)malloc(sizeof(*np));

Why the cast to (struct nlist *) on the last line? I can remove it without getting any compiler warnings.

I'm similarly confused by

free((void *)np->def);

Are these intended to aid readability somehow?

Coquelicot
  • 8,775
  • 6
  • 33
  • 37
  • The K&R books were written long before standards were finalized, and compilers at the time may have behaved differently. – Lee Daniel Crocker Apr 23 '15 at 23:24
  • Keep in mind that K&R was published in 1978 (2nd ed in '88), and styles change. – jpw Apr 23 '15 at 23:24
  • Is this in the latest addition? Pre-standardization compilers lacked a `void` type and had `malloc` returning `char *` or similar; even if it is the latest edition this might just be remnants of the old way of doing things. – R.. GitHub STOP HELPING ICE Apr 23 '15 at 23:24
  • And the code in the 2nd Edition was mainly tested with a C++ compiler (because the standard wasn't finalized and there weren't any C standard compilers), and the cast is mandatory in C++. – Jonathan Leffler Apr 23 '15 at 23:26
  • 1
    The 'official' (or consensus) view on things is expressed in [Do I cast the result of `malloc()`?](http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc). Like [Lee Daniel Crocker](http://stackoverflow.com/users/2192494/lee-daniel-crocker)'s [answer](http://stackoverflow.com/a/29836116/15168), I don't wholly subscribe to that view of life. – Jonathan Leffler Apr 23 '15 at 23:31
  • There is at least an ANSI-C version of K&R; no idea if there is any more recent release. – too honest for this site Apr 24 '15 at 00:04
  • If that code predates from K&R first edition, malloc was defined `int malloc(int);` then (well, not so, as there were no support for prototypes then) you have to cast it's return value (an `int`) to the proper pointer value (in that time, a pointer and an address shared the same size and characteristics) Examples for the second edition came from the first, so we switched from `int malloc();` to `caddr_t malloc(int);` to `(char *) malloc(size_t);` to `(void *) malloc(const size_t);`. The two first definitions return a `int` variant that might be type casted to the proper pointer. – Luis Colorado Apr 24 '15 at 05:19
  • @LuisColorado: before the C89 standard, the normal declaration for `malloc()` was `extern char *malloc();`, but there wasn't a standard header that declared it. The header `` provided access to an alternative, slightly configurable version of `malloc()` and included declarations for `malloc()` et al, but that was it. If you omitted a declaration, then the compiler inferred `extern int malloc();` just as it would infer a return type of `int` for anything else used as a function without a declaration. – Jonathan Leffler Apr 24 '15 at 17:43
  • Perhaps if you programmed in that time, you would have issues about malloc being called without a prototype in Turbo C large memory models, which crashed programs if you only did the cast without that header file. – Luis Colorado Apr 27 '15 at 05:05

4 Answers4

4

Casting the result of malloc was necessary in some pre-ANSI dialects of C, and the usage was retained in K&R2 (which covers the language as of the 1989 ANSI C standard).

This is mentioned in the errata list for the book. I've seen it via Dennis Ritchie's home page, which isn't currently available (I hope AT&T hasn't permanently removed it), but I found it elsewhere:

142(§6.5, toward the end): The remark about casting the return value of malloc ("the proper method is to declare ... then explicitly coerce") needs to be rewritten. The example is correct and works, but the advice is debatable in the context of the 1988-1989 ANSI/ISO standards. It's not necessary (given that coercion of void * to ALMOSTANYTYPE * is automatic), and possibly harmful if malloc, or a proxy for it, fails to be declared as returning void *. The explicit cast can cover up an unintended error. On the other hand, pre-ANSI, the cast was necessary, and it is in C++ also.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
3

Despite the opinion of the legions of posters here who will immediately jump on any code with an unnecessary (but harmless) cast of malloc(), the truth is that it just doesn't matter. Yes, assignment to and from void * does not require casting, but nor is it forbidden, and the arguments for leaving it in or taking it out really aren't that strong.

There are more important things to spend brain cells on. It just doesn't matter.

Lee Daniel Crocker
  • 12,927
  • 3
  • 29
  • 55
  • I disagree. An unncessary cast is always a potential problem, as it might hide a type-conflict if the type of the original object changed for some reason. While for malloc this might be a rare case, it happens quite often that the return-type of a function changes during development. Casting this will hide that change and prevent the compiler from reporting an error. In general, a cast should be the last resort and used sparesely. – too honest for this site Apr 23 '15 at 23:37
  • Yeah, I can't remember the last time I ever cast the return type of a function...I agree that if you think that's necessary then there's something wrong with your design, and if you get into the habit of it you might think it's a normal thing to do. – Lee Daniel Crocker Apr 23 '15 at 23:43
  • If you work with gcc and need not stick to some cast (literally, medical) like MISRA, have a look at the plan9-extensions. These avoid almost all casts in my projects (if I don't have to conform to MISRA&Co). – too honest for this site Apr 24 '15 at 00:02
  • 1
    In these days where an `int` and a pointer are even different sizes, if you don't `#include ` you'll get a `int malloc();` default prototype for malloc function (perhaps, you'll be only warned about that, but not more). In that case, you'll get only the 32 bit `int` result from the 64bit correct malloc pointer and convert it (extending even more the undefined behaviour) to a pointer again, resulting in an invalid pointer (not pointing to the same place as the original malloc returned) value. If you can live with this way of introducing bugs in the code, think twice at least. – Luis Colorado Apr 24 '15 at 05:30
2

For that example to be completely correct today, you have to put

#include <stdlib.h>

so you get the proper prototype for malloc(3). In this case, it's not important if you do a cast or not, as malloc is declared as returning void * there, and no need to cast from this type to another pointer type (but you can if you desire).

Today, it's better not to do the cast, as you can hide a more than frequent error. If you do the cast and don't provide a prototype to the compiler for malloc, the compiler assumes malloc is default declared as int malloc(); (returning an int instead of a void *, and taking an unspecified number of arguments) and you want (as you stated it explicitly) to convert that int to a pointer. The compiler will call malloc and take the supposed int result (the 32 bit value, not the actual, 64bit returned by malloc ---depending on the architecture calling conventions these values can be related or not, but they are always different, as the int space is smaller than the pointer space) as return value, convert it blindly to the cast type you propose (withoug warning, as you have explicitly put the cast, add the missing 32bits to complete a full 64bit pointer ---THIS IS TRULY DANGEROUS IF INTEGER TYPES ARE NOT THE SAME SIZE AS POINTER TYPES---, you can check this on 64bit platforms where they aren't, or in old MS-DOS compilers in large memory models, where they aren't also) and hide the real problem (which is that you did not provide a proper #include file) You will be lucky if it works, as that means all the virtual pointers returned by malloc are below the 0x100000000 limit (this in 64bit intel architectures, let's see in 64bit big endian architectures) This is the actual source of undefined behaviour you should expect.

Normally, with modern compilers, you'll probably get some kind of warning for using a function with no declaration (if you provided no prototype) but the compiler will compile the program and generate an executable program, probably not the one you want. This is one of the main differences between C and C++ languages (C++ don't allow you to compile code with a function call if you have not declared a prototype for it before, so you'll get an error, instead of a possible warning, if you get the invalid malloc I mention above)

This kind of errors are very difficult to target and that's the reason the people that advocates for not casting actually does.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31
  • What are you doing running your compiler in a mode where it would accept code that doesn't pre-declare all functions, as C99 and C11 both require? If you have a sane compiler and use sane flags so that undeclared functions are an error, you don't run into any problems. – Jonathan Leffler Apr 24 '15 at 17:45
  • @JonathanLeffler, unfortunately, not everybody has access to a C99 or C11 compiler (this includes even K&R C). Either case, I'm talking about C, not C99 or C11. Portability is an issue also. – Luis Colorado Apr 27 '15 at 05:01
  • You can develop on a system with a C99 or C11 compiler. You can ensure that the warnings about undeclared functions are enabled so your code never has any. You can then make sure your code still compiles under C89 if you must. – Jonathan Leffler Apr 27 '15 at 05:07
  • I do develop in such a system, but I have to compile and many times to allow to make modifications to the code in systems different to the development one. Perhaps this is the problem. Development is not the only phase where a program must be compiled. – Luis Colorado Apr 27 '15 at 05:10
  • But you will know from porting once if there is no `` to declare `malloc()` et al, and you'll then ensure you provide a surrogate that matches the K&R environment. Any standard C environment will provide its own ``. So, as long as the code compiles cleanly because `` is included, and you've verified this on your most stringent platform, you'll be OK everywhere. Well, unless your pre-standard platforms (how many of those are you really supporting?) are divergent and you need different headers for each. I am not convinced of the reality of your problem, yet. – Jonathan Leffler Apr 27 '15 at 05:16
1

The cast is deprecated. void * can be assigned directly to any pointer type; you actually even should do so. K&R is a bit outdated in some aspects and you should definitively get something more recent (and for newer standards - C99 upwards).

See n1570: 6.3.2.3/1.

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
  • Can you recommend a reference for modern C coding standards? I did a bit of googling beforehand, and found a huge mishmash of guides from various CS departments and open source projects. – Coquelicot Apr 23 '15 at 23:49
  • Well, actually no. If unsure, I have a look here;-) and if still in doubt, I check the standard or read the assembler-output of gcc (really!). The latter might not be the final instance, but it gives me at least a clue. AYdditionally, there are some websites which list common pitfalls in C like unsigned/signed conversion, bitops, etc. Actually, I only have one C-book in my shelf: K&R, the pre-ANSI(!) original :-) ... And guess when I last had a look into it. – too honest for this site Apr 23 '15 at 23:58