sharkbites
sharkbites

Reputation: 121

What is the point of "pointer types" when you dynamically allocate memory?

Why do we have pointer types? eg

int *ptr;

I know its for type safety, eg to dereference 'ptr', the compiler needs to know that its dereferencing the ptr to type int, not to char or long, etc, but as others outlined here Why to specify a pointer type? , its also because "we should know how many bytes to read. Dereferencing a char pointer would imply taking one byte from memory while for int it could be 4 bytes." That makes sense.

But what if I have something like this:

typedef struct _IP_ADAPTER_INFO {
    struct _IP_ADAPTER_INFO* Next;
    DWORD ComboIndex;
    char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4];
    char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4];
    UINT AddressLength;
    BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH];
    DWORD Index;
    UINT Type;
    UINT DhcpEnabled;
    PIP_ADDR_STRING CurrentIpAddress;
    IP_ADDR_STRING IpAddressList;
    IP_ADDR_STRING GatewayList;
    IP_ADDR_STRING DhcpServer;
    BOOL HaveWins;
    IP_ADDR_STRING PrimaryWinsServer;
    IP_ADDR_STRING SecondaryWinsServer;
    time_t LeaseObtained;
    time_t LeaseExpires;
} IP_ADAPTER_INFO, *PIP_ADAPTER_INFO;

PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO));

What would be the point of declaring the type PIP_ADAPTER_INFO here? After all, unlike the previous example, we've already allocated enough memory for the pointer to point at (using malloc), so isn't defining the type here redundant? We will be reading as much data from memory as there has been allocated.

Also, side note: Is there any difference between the following 4 declarations or is there a best practice?

PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO));

or

PIP_ADAPTER_INFO pAdapterInfo = (PIP_ADAPTER_INFO)malloc(sizeof(IP_ADAPTER_INFO));

or

IP_ADAPTER_INFO *pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO));

or

IP_ADAPTER_INFO *pAdapterInfo = (PIP_ADAPTER_INFO)malloc(sizeof(IP_ADAPTER_INFO));

Upvotes: 1

Views: 501

Answers (4)

chux
chux

Reputation: 153348

Why do we have pointer types?

To accommodate architectures where the size and encoding may differ for various types. C ports well to many platforms, even novel ones.


It is not unusual today that pointers to functions have a different size than pointers to objects. An object pointer coverts to a void *, yet a function pointer may not.

A pointer to char need not be the same size as a pointer to an int or union or struct. This is uncommon today. The spec details follow (my emphasis):

A pointer to void shall have the same representation and alignment requirements as a pointer to a character type. Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements. C11dr §6.2.5 28

Upvotes: 0

alk
alk

Reputation: 70903

What is the point of “pointer types” when you dynamically allocate memory?

At least for the example you show there is none.

So the follow up question would be if there were situations where typedefing a pointer made sense.

And the answer is: Yes.

It definitely makes sense if one is in the need of an opaque data type.

A nice example is the pthread_t type which defines a handle to a POSIX thread.

Depending on the implementation it is defined as

  • typedef struct bla pthread_t;
  • typedef struct foo * pthread_t;
  • typedef long pthread_t;

and with this abstracts away the kind of implementation, as it is of no interest to the user, which probably is not the intention with the struct you show in your question.

Upvotes: 1

John Bode
John Bode

Reputation: 123448

You’re kind of asking two different questions here - why have different pointer types, and why hide pointers behind typedefs?

The primary reason for distinct pointer types comes from pointer arithmetic - if p points to an object of type T, then the expression p + 1 points to the next object of that type. If p points to an 4-byte int, then p + 1 points to the next int. If p points to a 128-byte struct, then p + 1 points to the next 128-byte struct, and so on. Pointers are abstractions of memory addresses with additional type semantics.

As for hiding pointers behind typedefs...

A number of us (including myself) consider hiding pointers behind typedefs to be bad style if the user of the type still has to be aware of the type’s “pointer-ness” (i.e., if you ever have to dereference it, or if you ever assign the result of malloc/calloc/realloc to it, etc.). If you’re trying to abstract away the “pointer-ness” of something, you need to do it in more than just the declaration - you need to provide a full API that hides all the pointer operations as well.

As for your last question, best practice in C is to not cast the result of malloc. Best practice in C++ is to not use malloc at all.

Upvotes: 3

Steve Summit
Steve Summit

Reputation: 47923

I think this is more a question of type definition style than of dynamic memory allocation.

Old-school C practice is to describe structs by their tags. You say

struct foo {
    ...
};

and then

struct foo foovar;

or

struct foo *foopointer = malloc(sizeof(struct foo));

But a lot of people don't like having to type that keyword struct all the time. (I guess I can't fault then; C has always favored terseness, sometimes seemingly just to reduce typing.) So a form using typedef became quite popular (and it either influenced, or was influenced by, C++):

typedef struct {
    ...
} Foo;

and then

Foo foovar;

or

Foo *foopointer = malloc(sizeof(Foo));

But then, for reasons that are less clear, it became popular to throw the pointerness into the typedef, too, like this:

typedef struct {
    ...
} Foo, *Foop;


Foop foopointer = malloc(sizeof(*Foop));

But this is all a matter of style and personal preference, in the service of what someone imagines to be clarity or convenience or usefulness. (But of course opinions on clarity and convenience, like opinions on style, can legitimately vary.) I've seen the pointer typedefs disparaged as being a misleading or Microsoftian practice, but I'm not sure I can fault them right now.

You also asked about the casts, and we could also dissect various options for the sizeof call as the argument to malloc.

It doesn't really matter whether you say

Foop foopointer = (Foop)malloc(sizeof(*Foop));

or

Foop foopointer = (Foo *)malloc(sizeof(*Foop));

The first one may be clearer, in that you don't have to go back and check that Foop and Foo * are the same thing. But they're both poor practice in C, and in at least some circles they've been deprecated since the 1990's. Those casts are are considered distracting and unnecessary in straight C -- although of course they're necessary in C++, or I suppose if you're using a C++ compiler to compile C code. (If you were writing straight C++ code, of course, you'd typically use new instead of malloc.)

But then what should you put in the sizeof()? Which is better,

Foop foopointer = malloc(sizeof(*Foop));

or

Foop foopointer = malloc(sizeof(Foo));

Again, the first one can be easier to read, since you don't have to go back and check that Foop and Foo * are the same thing. But by the same token, there's a third form that can be even clearer:

Foop foopointer = malloc(sizeof(*foopointer));

Now you know that, whatever type foopointer points at, you're allocating the right amount of space for it. This idiom works best, though, if it's maximally clear that foopiinter is in fact a pointer that points at some type, meaning that the variants

Foo *foopointer = malloc(sizeof(*foopointer));

or even

struct foo *foopointer = malloc(sizeof(*foopointer));

can be considered clearer still -- and this may be one of the reasons people consider the pointer typedef to be less than perfectly useful.


Bottom line, if you're still with me: If you don't find PIP_ADAPTER_INFO useful, don't use it -- use IP_ADAPTER_INFO (along with explicit *'s when you need them) instead. Someone thought PIP_ADAPTER_INFO might be useful, which is why it's there, but the arguments in favor of its use aren't too compelling.

Upvotes: 1

Related Questions