Misunderstanding in particular user case of pointers and double-pointers

Viewed 94

I'm dealing with pointers, double-pointers and arrays, and I think I'm messing up a bit my mind. I've been reading about it, but my particular user-case is messing me up, and I'd appreciate if someone could clear a bit my mind. This is a small piece of code I've built to show my misunderstanding:

#include <stdio.h>
#include <stdint.h>

void fnFindValue_vo(uint8_t *vF_pu8Msg, uint8_t vF_u8Length, uint8_t **vF_ppu8Match, uint8_t vF_u8Value)
{
    for(int i=0; i<vF_u8Length; i++)
    {
        if(vF_u8Value == vF_pu8Msg[i])
        {
            *vF_ppu8Match = &vF_pu8Msg[i];
            break;
        }
    }
}

int main()
{
    uint8_t u8Array[]={0,0,0,1,2,4,8,16,32,64};
    uint8_t *pu8Reference = &u8Array[3];
    
    /*
     * Purpose: Find the index of a value in u8Array from a reference
     * Reference: First non-zero value
     * Condition: using the function with those input arguments
     */

    // WAY 1
    uint8_t *pu8P2 = &u8Array[0];
    uint8_t **ppu8P2 = &pu8P2;
    fnFindValue_vo(u8Array,10,ppu8P2,16); // Should be diff=4
    uint8_t u8Diff1 = *ppu8P2 - pu8Reference;
    printf("Diff1: %u\n", u8Diff1);
    
    // WAY 2
    uint8_t* ppu8Pos; // Why this does not need to be initialized and ppu8P2 yes
    fnFindValue_vo(u8Array,10,&ppu8Pos,64); // Should be diff=6
    uint8_t u8Diff2 = ppu8Pos - pu8Reference;
    printf("Diff2: %u\n", u8Diff2);
}

Suppose the function fnFindValue_vo and its arguments cannot be changed. So my purpose is to find the relative index of a value in the array taking as reference the first non-zero value (no need to find it, can be hard-coded).

In the first way, I've done it following my logic and understanding of the pointers. So I have *pu8P2 that contains the address of the first member of u8Array, and **ppu8P2 containing the address of pu8P2. So after calling the funcion, I just need to substract the pointers 'pointing' to u8Array to get the relative index.

Anyway, I tried another method. I just created a pointer, and passed it's address, without initializing the pointer, to the funcion. So later I just need to substract those two pointers and I get also the relative index.

My confusion comes with this second method.

  • Why ppu8Pos does not have to be initialized, and ppu8P2 yes? I.e. Why couldn't I declare it as uint8_t **ppu8P2;? (it gives me Segmentation fault).
  • Which of the two methods is more practical/better practice for coding?
  • Why is it possible to give the address to a pointer when the function's argument is a double pointer?
4 Answers

The address of a pointer (like e.g. &pu8P2) is a pointer to a pointer.

The result of &pu8P2 is a pointer to the variable pu8P2.

And since pu8P2 is of the type uint8_t * then a pointer to such a type must be uint8_t **.


Regarding ppu8Pos, it doesn't need to be initialized, because that happens in the fnFindValue_vo function with the assignment *vF_ppu8Match = &vF_pu8Msg[i].

But there is a trap here: If the condition vF_u8Value == vF_pu8Msg[i] is never true then the assignment never happens and ppu8Pos will remain uninitialized. So that initialization of ppu8Pos is really needed after all.


The "practicality" of each solution is more an issue of personal opinion I believe, so I leave that unanswered.

Why ppu8Pos does not have to be initialized, and ppu8P2 yes

You are not using the value of ppu8Pos right away. Instead, you pass its address to another function, where it gets assigned by-reference. On the other hand, ppu8P2 is the address of ppu8Pos you pass to another function, where its value is used, so you need to initialise it.

Which of the two methods is more practical/better practice for coding

They are identical for all intents and purposes, for exactly the same reason these two fragments are identical:

// 1
double t = sin(x)/cos(x);

// 2
double s = sin(x), c = cos(x);
double t = s/c;

In one case, you use a variable initialised to a value. In the other case, you use a value directly. The type of the value doesn't really matter. It could be a double, or a pointer, or a pointer to a pointer.

Why is it possible to give the address to a pointer when the function's argument is a double pointer?

These two things you mention, an address to a pointer and a double pointer, are one and the same thing. They are not two very similar things, or virtually indistinguishable, or any weak formulation like that. No, the two wordings mean exactly the same, to all digits after the decimal point.

For starters the function fnFindValue_vo can be a reason of undefined behavior because it does not set the pointer *vF_ppu8Match in case when the target value is not found in the array.

Also it is very strange that the size of the array is specified by an object of the type uint8_t. This does not make a sense.

The function should be declared at least the following way

void fnFindValue_vo( const uint8_t *vF_pu8Msg, size_t vF_u8Length, uint8_t **vF_ppu8Match, uint8_t vF_u8Value )
{
    const uint8_t *p = vF_pu8Msg;

    while ( p != vF_pu8Msg + vF_u8Length && *p != vF_u8Value ) ++p;

    *vF_ppu8Match = ( uint8_t * )p;
}

The difference between the two approaches used in your question is that in the first code snippet if the target element will not be found then the pointer will still point to the first element of the array

uint8_t *pu8P2 = &u8Array[0];

And this expression

 uint8_t u8Diff1 = *ppu8P2 - pu8Reference;

will yield some confusing positive value (due to the type uint8_t) because the difference *ppu8P2 - pu8Reference be negative.

In the second code snippet in this case you will get undefined behavior due to this statement

uint8_t u8Diff2 = ppu8Pos - pu8Reference;

because the pointer ppu8Pos was not initialized.

Honestly, not trying to understand your code completely, but my advice is do not overcomplicate it.

I would start with one fact which helped me untangle:

if you have int a[10]; then a is a pointer, in fact int x = a[2] is exactly the same like int x = *(a+2) - you can try it.

So let's have

int a[10]; //this is an array 
//a is a pointer to the begging of the array
a[2] is an int type and it is the third value in that array stored at memory location a plus size of two ints;
&a[2] is a pointer to that third value

*(a) is the first value in the array a
*(a+1) is the same as a[1] and it is the second int value in array a

and finally
**a is the same as *(*a) which means: *a is take the first int value in the array a (the same as above) and the second asterisk means "and take that int and pretend it is a pointer and take the value from the that location" - which is most likely a garbage.

https://stackoverflow.com/questions/42118190/dereferencing-a-double-pointer

Only when you have a[5][5]; then a[0] would be still a pointer to the first row and a[1] would be a pointer to the second row and **(a) would then be the same as a[0][0]. 

https://beginnersbook.com/2014/01/2d-arrays-in-c-example/

Drawing it on paper as suggested in comments helps, but what helped me a lot is to learn using debugger and break points. Put a breakpoint at the first line and then go trough the program step by step. In the "watches" put all variants like pu8P2,&pu8P2,*pu8P2,**pu8P2 and see what is going on.

Related