Difference Between *(Pointer + Index) and Pointer[]

Difference Between *(Pointer + Index) and Pointer[]

Functionally, they are identical.

Semantically, the pointer dereference says "Here's a thing, but I really care about the thing X spaces over", while the array access says "Here's a bunch of things, I care about the Xth one."

In most cases, I would prefer the array form.

What are pointer indices in C and what do they reference?

To help you better understand, lets try and make it more "graphical"...

Memory is a series of consecutive locations where values can be stored. After your assignment ip = (int*)p you can look at ip like this:


ip
|
v
+-------+-------+-------+----
| ip[0] | ip[1] | ip[2] | ...
+-------+-------+-------+----
^ ^ ^ ^
| | | |
100 104 108 112

Since ip is a pointer, there is really no specific end to the "array" it points to, the end you have to keep track of yourself.


Now when it comes to addressing, for simplicity's sake lets say that ip points to the address 100, that means that the contents of the variable ip is 100. Lets also say that sizeof(int) == 4.

Then when you do ip[2] it's the same thing as doing *(ip + 2), then you are adding 2 * sizeof(int) (i.e. 8) as byte offset to the pointer, and get address 108 which is then dereferenced giving you the contents of ip[2] (whatever that might be).


Now for a little curiosity and to "really fry your noodle": Thanks to the commutative property of addition, the expression *(ip + i) is of course the same as *(i + ip) which leads to the weird but valid transformation that ip[i] is the same as i[ip].

Why an array pointer with index returns the value in that index instead of returning the specific index adress?

The misunderstanding here is that pointers and arrays have similar behaviours in C, as in you can treat a pointer like an array, and an array like a pointer.

In effect x[n] is the same as *(x + n) and vice-versa. x[0] is just *x.

As such, ptr[1] will return a de-referenced int* or in other words an int.

If you want the actual address you need to do either ptr + n or &ptr[n], both of which are equivalent, they're int*.

C - when to use pointer arithmetic, when to use array indexing?

It usually depends on the situation. I don't think there's a rule of thumb.

In some cases, array indexes are better. For example when you have allocated an array

char* ptr = malloc(SIZE);

and you need the value of ptr to not change, because you want to free it later, then you can work with indexes.

Or if you get a pointer as a function argument

void func(char* ptr)

and you need to run over the array, then you can increment the pointer itself and you won't need to create a new variable to use as an index.

In most cases however, it depends on your own preferences.

Get array index from pointer difference in c or c++

&x[k] is the same as &x[0] + k.

Thus, p - &x[0] is &x[0] + 2 - &x[0], which is 2.

Pointer to pointer of structs indexing out of bounds(?) when I try to index anything other than zero

Let's start with a basic discussion about a pointer and a pointer-to-pointer. A pointer is simply a variable that holds the address of something else as its value. When you declare a pointer to something, as you have done with your name or near members within your struct you declare a variable that will hold the address in memory where that type object is stored in memory (e.g. the pointer will point to where that object is stored)

When you declare a pointer-to-pointer to type (e.g. Location **near) you have a pointer that holds the address of another pointer as its value. That can be useful in two ways. (1) it can allow you to pass the address of a pointer as a parameter so that the function is able to operate on the original pointer at that address, or (2) it can allow that single pointer to point to a collection of pointers in memory, e.g.

 pointer
| pointers allocated struct
near --> +----+ +-------------------+
| p1 | --> | struct Location 1 |
+----+ +-------------------+
| p2 | --> | struct Location 2 |
+----+ +-------------------+
| p3 | --> | struct Location 3 |
+----+ +-------------------+
| .. | | ... |

(a pointer-to-pointer to type struct Location)

In the second case, why choose a pointer-to-pointer as your type instead of just allocating for a collection of that type? Good question. There are two primary reasons, one would be if what you were allocating for can vary in size. For example:

 char**
| pointers allocated strings
words --> +----+ +-----+
| p1 | --> | cat |
+----+ +-----+--------------------------------------+
| p2 | --> | Four score and seven years ago our fathers |
+----+ +-------------+------------------------------+
| p3 | --> | programming |
+----+ +-------------------+
| .. | | ... |

or (2) where you want an allocated collection of an even number of objects (such as changing char** above to int**) that can be addressed using 2D-array indexing (e.g. array[2][7])

Allocating for a collection of pointers and objects adds complexity because you are responsible for maintaining two allocated collections, the pointers, and the objects themselves. You must track and reallocate for both your collection of pointers (and the objects -- if needed) and then free() your collection of objects before freeing your allocated block of pointers.

This can be greatly simplified, if you just need some number of the same type object, such as N - struct Location. That gives you a single allocation, single reallocation and single free for those objects themselves (of course each object can in turn contain allocated objects as well). In your case for near it would be similar to:

 pointer
|
near --> +-------------------+
| struct Location 1 |
+-------------------+
| struct Location 2 |
+-------------------+
| struct Location 3 |
+-------------------+
| ... |

(a pointer to type struct Location)

In your case you are dealing with needing nested allocated blocks of struct Location. In that sense, where required, you simply need N - struct Location which will all be of the same size and there isn't a compelling need for 2D array indexing. From that standpoint, looking at what you are trying to do (to the best possible guess), simply allocating for blocks of struct Location rather than handling separate blocks of pointers pointing to individually allocated struct Location would seem to make much more sense.

Implementing A Short-Example

While there is nothing wrong with an initLocation() to set up a single struct Location, you may find it makes more sense to simply write an addLocation() function to add a new struct Location to your collection each time it is called. If you initialize your pointer to the collection NULL back in the caller, you can simply use realloc() to handle your initial allocation and subsequent reallocations.

In the following example, we just create a new struct Location for each name in a list and allocate for 3-near objects. You are free to use addLocation() with the near struct Location in each object just as you have with your initial collection, but that implementation is left to you as it is simply doing the same thing on a nested basis.

Putting an addLocation() function together in a manner that looks like what you are attempting, you could do:

Location *addLocation (Location *l, size_t *nmemb, char *name, int nearCount)
{
/* realloc using temporary pointer adding 1 Location */
void *tmp = realloc (l, (*nmemb + 1) * sizeof *l); /* validate EVERY allocation */
if (!tmp) { /* on failure */
perror ("error: realloc-l");
return NULL; /* original data good, that's why you realloc to a tmp */
}

/* on successful allocation */
l = tmp; /* assign reallocated block to l */
l[*nmemb].isValid = 1; /* assign remaining values and */
l[*nmemb].name = name; /* allocate for near */
l[*nmemb].near = calloc(nearCount, sizeof(Location));
if (!l[*nmemb].near) {
perror ("calloc-l[*nmemb].near");
return NULL;
}
l[*nmemb].nearCount = nearCount; /* set nearCount */
(*nmemb)++; /* increment nmemb */

return l; /* return pointer to allocated block of Location */
}

You could then loop filling each with something similar to:

    for (size_t i = 0; i < nmemb;)  /* loop adding 1st nmemb names */
if (!(l = addLocation (l, &i, names[i], nearCount)))
break;

(note: i is being updated in addLocation so there is no need for i++ in your loop definition)

A complete example could be written as follows. I have added a print function and a function to delete all allocated memory as well. In the call to addLocation below, you will see names[i%nnames] used instead of names[i] and using the counter modulo the total number of names in my list just ensures that a name from the list is provided, no matter how big i gets.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

typedef struct Location {
char isValid;
char *name;
struct Location *near;
int nearCount;
} Location;

Location *addLocation (Location *l, size_t *nmemb, char *name, int nearCount)
{
/* realloc using temporary pointer adding 1 Location */
void *tmp = realloc (l, (*nmemb + 1) * sizeof *l); /* validate EVERY allocation */
if (!tmp) { /* on failure */
perror ("error: realloc-l");
return NULL; /* original data good, that's why you realloc to a tmp */
}

/* on successful allocation */
l = tmp; /* assign reallocated block to l */
l[*nmemb].isValid = 1; /* assign remaining values and */
l[*nmemb].name = name; /* allocate for near */
l[*nmemb].near = calloc(nearCount, sizeof(Location));
if (!l[*nmemb].near) {
perror ("calloc-l[*nmemb].near");
return NULL;
}
l[*nmemb].nearCount = nearCount; /* set nearCount */
(*nmemb)++; /* increment nmemb */

return l; /* return pointer to allocated block of Location */
}

void prn_locations (Location *l, size_t nmemb)
{
for (size_t i = 0; i < nmemb; i++)
if (l[i].isValid)
printf ("%-12s nearCount: %d\n", l[i].name, l[i].nearCount);
}

void del_all (Location *l, size_t nmemb)
{
for (size_t i = 0; i < nmemb; i++)
free (l[i].near); /* free each structs allocated near member */

free (l); /* free all struct */
}

int main (int argc, char **argv) {

char *endptr, /* use with strtoul conversion, names below */
*names[] = { "Mary", "Sarah", "Tom", "Jerry", "Clay", "Bruce" };
size_t nmemb = argc > 1 ? strtoul (argv[1], &endptr, 0) : 4,
nnames = sizeof names / sizeof *names;
int nearCount = 3; /* set nearCourt */
Location *l = NULL; /* pointer to allocated object */

if (errno || (nmemb == 0 && endptr == argv[1])) { /* validate converstion */
fputs ("error: nmemb conversion failed.\n", stderr);
return 1;
}

for (size_t i = 0; i < nmemb;) /* loop adding 1st nmemb names */
if (!(l = addLocation (l, &i, names[i%nnames], nearCount)))
break;

prn_locations (l, nmemb);
del_all (l, nmemb);
}

Example Use/Output

$ ./bin/locationalloc
Mary nearCount: 3
Sarah nearCount: 3
Tom nearCount: 3
Jerry nearCount: 3

Or, for example if you wanted to allocate for 10 of them, then:

$ ./bin/locationalloc 10
Mary nearCount: 3
Sarah nearCount: 3
Tom nearCount: 3
Jerry nearCount: 3
Clay nearCount: 3
Bruce nearCount: 3
Mary nearCount: 3
Sarah nearCount: 3
Tom nearCount: 3
Jerry nearCount: 3

Memory Use/Error Check

In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

It is imperative that you use a memory error checking program to ensure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.

For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.

$ valgrind ./bin/locationalloc
==13644== Memcheck, a memory error detector
==13644== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==13644== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==13644== Command: ./bin/locationalloc
==13644==
Mary nearCount: 3
Sarah nearCount: 3
Tom nearCount: 3
Jerry nearCount: 3
==13644==
==13644== HEAP SUMMARY:
==13644== in use at exit: 0 bytes in 0 blocks
==13644== total heap usage: 9 allocs, 9 frees, 1,728 bytes allocated
==13644==
==13644== All heap blocks were freed -- no leaks are possible
==13644==
==13644== For counts of detected and suppressed errors, rerun with: -v
==13644== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Always confirm that you have freed all memory you have allocated and that there are no memory errors.

Let me know if this comports with your intent and whether you have any additional questions.



Related Topics



Leave a reply



Submit