2

I use lfind with this code:

int StringPtrCompare(const void *ptr1, const void *ptr2)
{
   return strcmp(*(char(*)[])ptr1, *(char**)ptr2);
}

void Unique()
{
    char*  array[1000] = {0};
    char   buffer[200] = {0};
    size_t count       = 0;
    FILE*  fp          = fopen("text", "r");

    if (fp == NULL)
    { 
        error(1, 0, "Couldn't open text for reading");
    }

    while (count < 1000 && fscanf(fp, "%199s", buffer) == 1)
    {
        if (!lfind(buffer, array, &count, sizeof(char*), StringPtrCompare))
        {
            array[count++] = strdup(buffer);
        }
    }

    fclose(fp);

    for (int i = 0; i < count; i++)
    {
        printf("%s\n", array[i]);
        free(array[i]);
    }
}

This works, but initially, I was getting segmentation faults if instead, in StrPtrCompare, I replaced the cast of the first parameter in strcmp with

*(char**)ptr1 

I don't understand, conceptually, why this was the case. Isn't

(char(*)[]) equivalent to (char**)? 

Also, I find that if I replace buffer with &buffer in the while loop statement, the function still works.

Can anyone please explain these two oddities to me?

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
pyrrhic
  • 1,769
  • 2
  • 15
  • 27
  • I see no function pointers here. –  Aug 06 '13 at 13:32
  • Also, here's the answer to your last question: [link](http://stackoverflow.com/questions/2528318/how-come-an-arrays-address-is-equal-to-its-value-in-c) –  Aug 06 '13 at 13:33
  • "Function pointers -- `char(*)[]` vs `char**`" - neither of the enumerated pointer types is a pointer-to-function type. Your question has nothing to do with function pointers. –  Aug 06 '13 at 13:34
  • Okay I edited it. Thanks! Any ideas for the first part? – pyrrhic Aug 06 '13 at 13:36
  • 2
    Read the [C FAQ](http://c-faq.com/questions.html), it really helps understand stuff, even up to small details. Not a big time investment. – meaning-matters Aug 06 '13 at 13:36
  • @pyrrhic I'm **trying** very hard to understand your code. It's quite messy, TBH. –  Aug 06 '13 at 13:37

4 Answers4

4

&buffer and buffer will have same value.Name of an array acts as pointer to its first element that is why you are getting both as same.

Now if we talk about your second problem (char(*)[]) equivalent to (char**)?

(char(*)[]) is not a function pointer.It is pointer to an array of characters. Array and pointers are not same.Array may decay as pointer.

Please have a look here

Dayal rai
  • 6,548
  • 22
  • 29
1

char(*)[] is a pointer to an array whereas char** is a pointer to pointer.

And no, these are not equivalent, for example if you have char (*arr)[10], here arr can only be assigned to array of the same size and type. Furthermore you have to pass the address of the array which you do not need to do if you want a pointer to point to an array.

Consider the following usage:

char arr[10];

char brr[10];

char (*crr)[10] = &arr; /* note the address-of operator, just passing 'arr' will incense the compiler */

crr = &brr; /* again reassigning but to an array of valid type */

Contrast this with char**:

char *twod_arr[5];

char *twod_brr[4];

char** drr = twod_arr; /* legal, doesn't care about size of array */

drr = twod_brr; /* different array size, still doesn't matter */
Nobilis
  • 7,310
  • 1
  • 33
  • 67
  • Isn't arrin char(*arr)[10] a pointer, though? I thought only arrays couldn't be incremented/decremented? – pyrrhic Aug 06 '13 at 13:42
  • @pyrrhic Sorry, misread question, I've updated my answer. Yes, you cannot increment arrays as in `int arr[10]; arr++;` they are called `non-modifiable lvalue` which in effect means that you can get its address but not update its value. Pointers are fine. – Nobilis Aug 06 '13 at 13:55
1

What ptr1 actually represents is the value supplied to lfind(), which is the decayed pointer value of the array buffer.

What you have cast ptr1 to is a pointer to an array of char. Dereferenced, that expression yields an array of char. An array in an expression decays to a value equal to the address of its first element. This works out for you because it so happens that &buffer and buffer have the same pointer value (although they have different types). In the end, the result of the first argument to strcmp() is the same as what was passed to lfind(), the decayed pointer value of buffer.

When you cast ptr1 as (char **) instead, this is a pointer to a pointer to char. Deferencing that yields a pointer to char. However, since ptr1() actually has the decayed pointer value of the array buffer, the result of this dereference causes strcmp() to get sizeof(char *) bytes of buffer treated as a pointer to char.

In either case, you have dereferenced a pointer with a type that is incompatible with the object that is actually located at that address, which causes undefined behavior. In one case, it happened to work out, in the other case, it crashed.

You can instead treat ptr1 as the type that it actually is, a pointer to char:

int StringPtrCompare(const void *ptr1, const void *ptr2) {
   return strcmp((const char *)ptr1, *(char**)ptr2);
}
jxh
  • 69,070
  • 8
  • 110
  • 193
  • Why does ptr1 represent the decayed pointer value of buffer? Doesn't the manual say that it's a "pointer to the key object"? Isn't the key here the buffer array? So shouldn't it be a pointer to the array, buffer? – pyrrhic Aug 06 '13 at 18:55
  • @pyrrhic: You have already provided the pointer to the key object when you passed it into the `lfind()` call. `lfind()` is just returning it back in the call to the comparison function. – jxh Aug 06 '13 at 19:03
  • So the entire "pointer to the key object" is the buffer array itself? But shouldn't the entire key object be the buffer array itself? So shouldn't a pointer to the keyobject, that is, a pointer to the array, be passed to the comparison function? Sorry if I'm being stupid, I don't know if I'm reading the manual (on Linux) correctly. – pyrrhic Aug 06 '13 at 19:06
  • @pyrrhic: I think you are overthinking this. Consider if you were searching over an array of `int` with `lfind()`. You would create an `int` variable, give it the number you wanted to search for, and pass in the address of that variable. In your compare function, you would just get back the pointer you passed in, not a pointer to the pointer you passed in. The point is, `lfind()` passes to the comparison function what was passed to it. – jxh Aug 06 '13 at 19:15
1

Fixing the code

Your code passes things of the wrong type to lfind. If the key argument is a pointer to some type T, then the base argument must be a pointer to an array of T. When you pass buffer as key, then buffer, which is an array of char, is automatically converted to a pointer to char. Thus, T is char. So base is required to be an array of char. However, your base is an array of pointers to char, so it is the wrong type.

To fix this, add this code in Unique, after buffer is defined:

char *BufferPointer = buffer;

Then, to call lfind, use:

if (!lfind(&BufferPointer, &array, &count, sizeof(char*), StringPtrCompare))

With this call, the first parameter is a pointer to a pointer to char. Thus, the type T is pointer to char. This matches &array, which is a pointer to an array of pointer to char.

Then, in StringPtrCompare, the parameters ptr1 and ptr2 will each have type pointer to T. Since T is pointer to char, the type of ptr1 and ptr2 is pointer to pointer to char. (I am ignoring the const qualifier for now.) Then you can call strcmp this way:

int StringPtrCompare(const void *ptr1, const void *ptr2)
{
   return strcmp(* (char **) ptr1, * (char **) ptr2);
}

Pointers to arrays versus pointers to pointers

A pointer to an array, such as char (*)[], is not the same things as a pointer to a pointer, such as char **. If p is a pointer to an array, then, at the place where p points, there must be an array of elements: The data of the elements must be at that location.

In contrast, if p is a pointer to a pointer, then, at the place where p points, there must be a pointer: The bytes at that location must contain the value of a pointer.

An array versus the address of an array

When buffer has type char [200], it is an array of char. When you use an array in an expression, it is converted to a pointer to its first element, unless it is the operand of sizeof, _Alignof, or & or is a string literal used to initialize an array. When you pass buffer to lfind, none of those exceptions applies, so buffer is converted to the address of its first char.

When you pass &buffer, this expression is the address of the array. The address of the array has the same value as the address of its first element, but has a different type. However, when you pass either buffer or &buffer to lfind, it is converted to void *, because lfind is declared to take an argument of that type. Thus, the difference between the types of buffer and &buffer is lost, and only the value is passed. Since they have the same value, there is no difference.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312