1

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));
sharkbites
  • 101
  • 1
  • 8
  • 3
    First of all, [should you really cast the result of `malloc` in C](http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc)? As for using `typedef` to define an alias for a pointer type, it's very much a Windows API convention and outside of that it's not generally recommended (as it tends to hide the actual type, making it harder to understand that it's a pointer). – Some programmer dude Jun 17 '18 at 11:10
  • 4
    I think your question is not "What is the point of 'pointer types'?", but rather "Why do some programmers define struct pointer typedefs?". And that's a very different question. – Steve Summit Jun 17 '18 at 11:12
  • Regarding your first link: I'm using Visual Studio in Windows, and without the casting it says "a value of type 'void *' cannot be used to initialize an entity of type 'PIP_ADAPTER_INFO' – sharkbites Jun 17 '18 at 11:20
  • 3
    Because you are using a C++ compiler to compile C code. (which is considered a bad habit) In C, you can cast *any* pointer type to/from `void*` [function pointers are different] – wildplasser Jun 17 '18 at 11:22
  • @wildplasser I wouldn't consider that a certainty. It wouldn't surprise me if a version or seven of MSVC forced C++ features such as the requirement to cast a `void *` into C. MS support for actual C has been rather poor over the years. See https://stackoverflow.com/questions/9610747/which-c99-features-are-available-in-the-ms-visual-studio-compiler for just one example. – Andrew Henle Jun 17 '18 at 11:26
  • 1
    @wildplasser ... in C *any* pointer type **is implicitly converted** to/from `void*` ... (SCNR) – alk Jun 17 '18 at 11:30
  • WRT standards, best is IMHO to ignore MS. It takes too much energy just to remember their exceptions and extensions. @alk: you are correct of course. – wildplasser Jun 17 '18 at 11:31
  • 2
    Typedefing pointer types is widely considered a bad practice, just don't do this. And `_IP_ADAPTER_INFO` is a *reserved* identifier, as is any other beginning with an underscore and either a capital letter or another underscore. User code should not define such identifiers. – n. m. could be an AI Jun 17 '18 at 12:18

4 Answers4

3

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.

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

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.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • 1
    And even: `struct foo *foopointer = malloc(sizeof *foopointer);` , which stipulates the fact that foopointer is *not* a type. – wildplasser Jun 17 '18 at 11:45
  • @Steve , Ok , I think I get it... So are you saying that `int * ptr` and `IP_ADAPTER_INFO *pAdapterInfo` are not equivalent? The first is about type safety and as my first link suggested, the length of data for the compiler to dereference/read; and the latter is just a 'laziness' that occurred over time because people started using typedefs? – sharkbites Jun 17 '18 at 12:12
  • 1
    Still not 100% sure what you're asking. In C, the distinction between "thing" and "pointer to thing" is hugely significant. You have to know that you have a pointer; there has to be a `*` somewhere. You can't say `x.m` when you mean `x->m`, or vice versa. Hiding the `*` behind a typedef is either a convenience or an obfuscation, depending on your opinion. See also @alk's answer. – Steve Summit Jun 17 '18 at 12:16
1

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.

alk
  • 69,737
  • 10
  • 105
  • 255
0

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

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256