How to Use Lfind
I Use Lfind with This Code: Int Stringptrcompare(Const Void *Ptr1, Const Void *Ptr2) { Return Strcmp(*(Char(*)[])Ptr1, *(Char**)Ptr2); } Void Unique() { Char*...
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?
4 Answers
&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
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 */
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);
}
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.