Why is it necessary to cast NULL to a type in this macro?
Asked Answered
M

2

5

I have a question about some code in Eric Roberts' Programming Abstractions in C. He use several libraries of his own both to simplify things for readers and to teach how to write libraries. (All of the library code for the book can be found on this site.)

One library, genlib provides a macro for generic allocation of a pointer to a struct type. I don't understand part of the macro. I'll copy the code below, plus an example of how it is meant to be used, then I'll explain my question in more detail.

/*
 * Macro: New
 * Usage: p = New(pointer-type);
 * -----------------------------
 * The New pseudofunction allocates enough space to hold an
 * object of the type to which pointer-type points and returns
 * a pointer to the newly allocated pointer.  Note that
 * "New" is different from the "new" operator used in C++;
 * the former takes a pointer type and the latter takes the
 * target type.
 */

#define New(type) ((type) GetBlock(sizeof *((type) NULL)))

/* GetBlock is a wrapper for malloc. It encasulates the 
 * common sequence of malloc, check for NULL, return or
 * error out, depending on the NULL check. I'm not going
 * to copy that code since I'm pretty sure it isn't
 * relevant to my question. It can be found here though:
 * ftp://ftp.awl.com/cseng/authors/roberts/cs1-c/standard/genlib.c
 */

Roberts intends for the code to be used as follows:

    typedef struct {
        string name;
        /* etc. */
    } *employeeT;
    employeeT emp;
    emp = New(employeeT);

He prefers to use a pointer to the record as the type name, rather than the record itself. So New provides a generic way to allocate such struct records.

In the macro New, what I don't understand is this: sizeof *((type)) NULL). If I'm reading that correctly, it says "take the size of the dereferenced cast of NULL to whatever struct type type represents in a given call". I think I understand the dereferencing: we want to allocate enough space for the struct; the size of the pointer is not what we need, so we dereference to get at the size of the underlying record-type. But I don't understand the idea of casting NULL to a type.

My questions:

  1. You can cast NULL? What does that even mean?
  2. Why is the cast necessary? When I tried removing it, the compiler says error: expected expression. So, sizeof *(type) is not an expression? That confused me since I can do the following to get the sizes of arbitrary pointers-to-structs:

    #define struct_size(s_ptr) do { \
        printf("sizeof dereferenced pointer to struct %s: %lu\n", \
               #s_ptr, sizeof *(s_ptr)); \
    } while(0)
    

Edit: As many people point out below, the two examples aren't the same:

/* How genlib uses the macro. */
New(struct MyStruct*)
/* How I was using my macro. */
struct MyStruct *ptr; New(ptr)

For the record, this isn't homework. I'm an amateur trying to improve at C. Also, there's no problem with the code, as far as I can tell. That is, I'm not asking how I can do something different with it. I'm just trying to better understand (1) how it works and (2) why it must be written the way it is. Thanks.

Mosira answered 7/6, 2014 at 20:17 Comment(2)
Your struct_size macro is invalid if s_ptr isn't an expression. If you pass struct MyStruct * as s_ptr, the code won't compile, since sizeof *(struct MyStruct *) is not valid C... And no, *(type) isn't an expression because it wouldn't make sense - what exactly did you expect *(type) to evaluate to?Laruelarum
@FilipeGonçalves I don't have a good answer for what I expected. I was confused. That's why the question.Mosira
M
5

The issue is that the macro needs to get the size of the type pointed at by the pointer type.

As an example, suppose that you have the the pointer type struct MyStruct*. Without removing the star from this expression, how would you get the size of struct MyStruct? You couldn't write

sizeof(*(struct MyStruct*))

since that's not legal C code.

On the other hand, if you had a variable of type struct MyStruct*, you could do something like this:

struct MyStruct* uselessPointer;
sizeof(*uselessPointer);

Since sizeof doesn't actually evaluate its argument (it just determines the static size of the type of the expression), this is safe.

Of course, in a macro, you can't define a new variable. However, you could make up a random pointer to a struct MyStruct* by casting an existing pointer. Here, NULL is a good candidate - it's an existing pointer that you can legally cast to a struct MyStruct*. Therefore, if you were to write

sizeof(* ((struct MyStruct*)NULL))

the code would

  1. Cast NULL to a struct MyStruct*, yielding a pointer of static type struct MyStruct*.
  2. Determine the size of the object that would be formed by dereferencing the pointer. Since the pointer has type struct MyStruct*, it points at an object of type struct MyStruct, so this yields the type of struct MyStruct.

In other words, it's a simple way to get an object of the pointer type so that you can dereference it and obtain an object of the underlying type.

I've worked with Eric on some other macros and he is a real pro with the preprocessor. I'm not surprised that this works, and I'm not surprised that it's tricky, but it certainly is clever!

As a note - in C++, this sort of trick used to be common until the introduction of the declval utility type, which is a less-hacky version of this operation.

Hope this helps!

Mountbatten answered 7/6, 2014 at 20:23 Comment(8)
It is very helpful overall. My only remaining question (as with Oli's answer) is why *(type) by itself can't be evaluated by sizeof. In my silly macro (later in the question), I do that in a call to printf. How are the two different?Mosira
There's no fundamental reason why this couldn't be evaluated inside of sizeof, but the language doesn't allow this. The only options for sizeof are sizeof(type-name) or sizeof expression, and they can't be mixed.Mountbatten
Is the use in my macro with printf not an expression? Maybe that's what I'm unsure of. Why is *(type) not an expression?Mosira
@Mosira I'm not sure that your macro works. If you pass in an actual pointer, then it works fine because *(s_ptr) means "dereference s_ptr" If you pass in a typename, it doesn't compile on my system.Mountbatten
I pass in pointers. I can confirm mine works for that use case: gist.github.com/telemachus/62c72005e8d514c58014. But that is how (or one way) that Roberts uses his macro too. So I'm stuck not understanding the relevant difference.Mosira
@Mosira In this version, you'd write something like New(struct MyStruct*) rather than struct MyStruct *ptr; New(ptr). In other words, the argument is the name of a type, not a pointer.Mountbatten
@Telemachus: What would you expect to be the value of an expression like *(type)? Say, type is pointer to T, then T is a type, not a value.Sugary
Thanks templatetypedef and @Sugary I wasn't clearly focusing on the difference between passing the type's name versus passing a pointer value of that type. I think I get it now.Mosira
S
4

It's a hack. It relies on the fact that the argument to the sizeof operator isn't actually evaluated.

To answer your specific questions:

  1. Yes, NULL is just a pointer literal. Like any other pointer, it may be cast.

  2. sizeof operates on either a type or an expression. *(type) would be neither (after macro substitution has occurred), it would be a syntax error.

Subdiaconate answered 7/6, 2014 at 20:23 Comment(3)
Can you say a little bit more about why *(type) is neither a type nor a value? In my silly macro later in the question, I use the equivalent in a printf call, don't I? How is that different?Mosira
@Telemachus: Presumably because in that example, s_ptr is an expression, not a type?Subdiaconate
Yup. As I said in my final comment to templatetypedef's answer, I didn't think enough about the distinction between passing the type's name and an actual pointer to the type. I accepted his answer as fuller, but thanks for yours very much. This was very helpful.Mosira

© 2022 - 2024 — McMap. All rights reserved.