4

The following struct is given:

typedef struct {    
    int a;    
    int b;    
    int c; 
} POST, *PPOST;

The assignment is to make a function int compare_post( PPOST pp, POST p); that checks if there is identical copy of p in the "array" pp, which ends with a null-pointer.

The correct answer is the following:

int compare_post( PPOST pp, POST p) {
       while( pp ){
            if((pp->a == p.a )&&(pp->b == p.b )&&(pp->c == p.c ))
               return 1;            
            pp++;     
       }
      return 0; 
}

My question is why while(pp) is used and not while(*pp)? Don't we need dereference pp to get the actual value in order to detect where it is NULL?

matthias_h
  • 11,356
  • 9
  • 22
  • 40
user3658609
  • 539
  • 2
  • 6
  • 14
  • 3
    You’re right that the “correct” answer is not correct. Edit: it doesn’t make sense either way, though, as both Asadefa and Sunburst275 have pointed out. An array can’t end with a null pointer if it’s not an array of pointers, and `PPOST` isn’t. – Ry- Mar 14 '20 at 23:11
  • 1
    The input argument for `pp` is not a `PPOST *pp`, but a `PPOST pp`, which is no pointer. You dont use the pointer. At least it looks like that for me. I dont know wether it shouldnt be a `PPOST *pp` instead of `PPOST pp`. – Vandrey Mar 14 '20 at 23:16
  • 3
    Perhaps feedback to whoever gave you this code: Not only is the answer wrong but teaching people to typedef pointers is arguably not good practice. Such typedefs obscure the real type, make it harder to understand, and make it more error prone. – kaylum Mar 14 '20 at 23:18
  • 5
    See [Is it a good idea to typedef pointers?](https://stackoverflow.com/q/750178/15168). TL;DR — the general answer is "No", with possible exceptions for function pointers. – Jonathan Leffler Mar 14 '20 at 23:40
  • Ignore `PPOST` and treat it as if the typedef had not been given. In order to iterate until a *sentinel NULL* is reached, `pp` must be a *pointer-to-pointer-to* `POST` not *pointer-to* `POST`. Your parameter must be type `POST **`. (resist the urge to further bastardize the typedeffed pointer) Then it would make sense to derefernce `pp` to check `NULL` or equality with the members of `p`. – David C. Rankin Mar 15 '20 at 01:27
  • @Sunburst275 PPOST is a pointer type – M.M Mar 15 '20 at 07:50
  • Why can’t you just do p == *pp ? They are int objects. So you can just compare the two structures with ==. – Andrew Truckle Mar 15 '20 at 08:04
  • @M.M aaaah now i get it. I should read the post more carefully in the future. thank you! – Vandrey Mar 15 '20 at 09:49
  • @user3658609 Hi, answer of Vlad Kaponir makes sence - add some contrarguments or please don't forget to accept correct solution :) –  Mar 17 '20 at 01:27

3 Answers3

0

The assignment does not make sense, because the function prototype

int compare_post( PPOST pp, POST p);

mentioned in the assignment does not make sense. The function parameter pp can't be a pointer to the first element of an array terminated by a NULL pointer. If an array is to be terminated by a NULL pointer, the array must have a pointer data type, i.e. it must be an array of pointers. However, if the array has a pointer data type, then a pointer to the first element of that array must be a pointer to a pointer. In other words, there must be two levels of pointer indirection. Therefore, the first parameter of the function must be changed to the following:

int compare_post( PPOST *pp, POST p );

which is equivalent to:

int compare_post( POST **pp, POST p );

For clarity, and because some people consider creating typedefs for pointers inappropriate, I will use the second syntax.

With this new function prototype, the correct answer would be the following:

int compare_post( POST **pp, POST p ) {
    while ( *pp ) {
        if ( ( (*pp)->a == p.a ) && ( (*pp)->b == p.b ) && ( (*pp)->c == p.c ) )
            return 1;
        pp++;
    }
    return 0;
}
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • A lot of code use this practice during decades - typedef struct tagName {...} Name, *PName; It's too late for dispute. Coders should be familiar with this idiom. And it allows to change understanding data structure. Yes, it provokes mistakes as we can see in this post. But mistakes may be explained by false statement in question: author claims that function is correct and everyone relied on this. –  Mar 15 '20 at 02:05
  • @Myshkin: As a reaction to your comment, I have now changed the formulation in my answer to state that "some people consider creating typedefs for pointers inappropriate" instead of claiming that it is "generally considered inappropriate". – Andreas Wenzel Mar 15 '20 at 03:56
  • @Andreas Wenzel Thank you! To be more exact I want to emphesize that despite the fact that your answer are similar to answer of Kaponir - time of your answer was later only on a couple of minutes - so both of your answers are independent. And this increases our confidence in their correctness. –  Mar 15 '20 at 04:11
-1

Written without typedefs to clarify that p is a struct and pp is a pointer,

struct Post { int a, b, c; };

int compare_post(struct Post *pp, struct Post p) {
    while(pp) {
        if((pp->a == p.a) && (pp->b == p.b) && (pp->c == p.c))
            return 1;            
        pp++;     
    }
    return 0;
}

While it makes syntactic sense to say while(pp), which is a pointer comparison that enters the loop as long as pp is not null, it almost certainly is a logical error. A null-pointer is "guaranteed to compare unequal to a pointer to any object or function". So, if pp is a valid object of struct Post, it will repeat the comparison until an array element is pairwise equal, returning 1, or go off the side of the array and cause undefined behaviour. The only way to call this safely is pass a null pointer as pp (returns 0) or by engineering any in the pp array equal to p, (returns 1.) By the function name, I would expect something like this:

int compare_post(struct Post *x, struct Post *y) {
    return (x->a == y->a) && (x->b == y->b) && (x->c == y->c);
}

This is at a different level of indirection from C strings, which, in order to represent a variable-length, they use a sentinel NUL. This character is all-bits-zero, and is represented by the escape-sequence '\0', as well as a explicit cast (char)0. This is not null-pointer in the previous context.

Neil
  • 1,767
  • 2
  • 16
  • 22
  • 1
    This answer should state more clearly that the code is wrong, both as shown in the question and as shown in the answer. The assignment states there is supposed to be an array that ends with a null pointer, and there is none, either in the code in the question or the code in this answer. – Eric Postpischil Mar 14 '20 at 23:45
  • @EricPostpischil why do you think the code is incorrect? it clearly states in the question that pp is an array that ends in a null pointer. – Adham Zahran Mar 15 '20 at 00:31
  • 3
    @AdamZahran: `pp` is a pointer to a structures. If it points to an array, that array is an array of structures, and structures cannot be null. If `pp` is in an array of pointers, we have not been given the address of that array or of `pp`; we have only been given the value of `pp`. So we have no way to look through that array to see if there is a null pointer in it. The only address we have is the value of `pp`. If it is not null, then `pp+1` is also not null, and neither is `pp+2`, `pp+3`, `pp+4`, and so on, until possibly we go beyond the array and violate rules of pointer arithmetic. – Eric Postpischil Mar 15 '20 at 00:37
  • You are right, it does state that it ends in a null pointer; I updated this to explain why this doesn't make sense. – Neil Mar 15 '20 at 02:35
-1

The better title for this question is to how to pass array to function. The function in question is incorrect - to force this approach to work we need to change signature - to pass pointer to PPOST. Most likely author of this code relies on idea (not standard) that end of sequence is specified with special value NULL (sentinel), that is for array (allocated for N + 1 pointers) after N not empty elements (pointers to structure) follows N + 1 element (pointer) that will be equal to zero. This zero pointer will mark the end of range. And of course this is responsibility of coder - to mark the end of array of pointers by assigning last element to NULL before calling the function. Do following before calling a function:

// allocate array of pointers
PPOST *ptr = (PPOST*) malloc((N + 1)*sizeof(PPOST*));
ptr + N = NULL; // mark the end - sentinel
POST p;
// ---
compare_post( ptr, p)
// ---
int compare_post( PPOST *pp, POST p)
{
   while( *pp ){
        if(((*pp)->a == p.a )&&((*pp)->b == p.b )&&((*pp)->c == p.c ))
           return 1;            
        pp++;     
   }
  return 0; 
}

You should test the pointer - dereferenced pointer to pointer (*pp) - not value of structure **pp. That is you will iterate through array until you reach N + 1 element.

More common approach (especially widely used in C++ std library) is to pass too pointers to specify boundaries of range: begin of sequence and one past the last: [beg, end). Where end is not included in range. This allows to avoid creating of tedious array of pointers:

// allocate array of struct POST
PPOST ptr = (PPOST) malloc( N * sizeof(POST));
// fill structures
PPOST onePastTheEnd = ptr + N;
POST p;
// ---
compare_post( ptr, onePastTheEnd, p)
// ---
int compare_post( PPOST pp, PPOST end, POST p)
{
   while( pp != end ){
        if((pp->a == p.a )&&(pp->b == p.b )&&(pp->c == p.c ))
           return 1;            
        pp++;     
   }
  return 0; 
}

As soon as we start to change function signature we can also pass the size of the array - this is another approach.

Vlad
  • 1,977
  • 19
  • 44
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/209651/discussion-on-answer-by-vlad-kaponir-structs-and-pointers-in-c-used-in-a-while-l). – Bhargav Rao Mar 15 '20 at 03:27