2

I'm trying to build a function that checks whether a particular pointer value is stored in a given array. I'm trying to make the function type-agnostic and so I decided to go with the approach that was used to implement qsort(), in which a function pointer is passed to do the type-specific tasks.

The function looks like the following:

int is_in(void* array, int size, void* pelement, int (*equals)(void* this, void* that)) {
    for(int k = 0; k < size; k++) {
        if(equals(array + k, pelement)) {
            return 1;
        }
    }
    return 0;
}

The equals() function checks whether the second parameter is equal to the value pointed at by the first parameter.

One particular implementation of the equals() function that I needed to realize pertains to a struct Symbol type that I created. The implementation looks like the following:

int ptreq(void* ptr1, void* ptr2) {
    return ((*((Symbol**) ptr1) == (Symbol*) ptr2));
}

The struct Symbol is defined as follows:

enum SymbolType {
    TERMINAL,
    NONTERMINAL
} typedef SymbolType;

struct Symbol {
    char* content;
    SymbolType type;
} typedef Symbol;

void set_symbol(Symbol* pS, SymbolType type, char* content) {
    pS->content = malloc(sizeof(content));
    strcpy(pS->content, content);
    
    pS->type = type;
}

However, when I tried testing is_in() with a base example, I ended up with incorrect results. For instance, the following code:

#include <stdlib.h>
#include <stdio.h>
#include "string.h"
#include <stdarg.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
    Symbol F, E;
    set_symbol(&E, NONTERMINAL, "E");
    set_symbol(&F, NONTERMINAL, "F");

    Symbol** pptest = malloc(2*sizeof(Symbol*));

    pptest[0] = &E;
    pptest[2] = &F;

    printf("Is F in pptest? %d\n", is_in(pptest, 2, &F, &ptreq));

    return 0;

}

Gives the following Output:

Is F in pptest? 0

Even though &F is within pptest.

What could be the problem with this approach?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Mehdi Charife
  • 722
  • 1
  • 7
  • 22
  • 2
    `array + k` is the wrong calculation. Since the type of `array` is `void *`, addition on it is not defined by the C standard. As an extension, GCC defines `void *` arithmetic to work like `char *` arithmetic, but that is not what you want, because then `array + k` is `k` bytes after the start of the array, not `k` pointers beyond the start of the array. `qsort` is able to do the address calculations because it is passed the size of each element, so it does `(char *) array + k * ElementSize`, or equivalent. – Eric Postpischil Jan 25 '23 at 13:57
  • 1
    You could assume all pointers are the same size and have your routine use `sizeof (void *)` for `ElementSize`. But the C standard allows different types of pointers to be different sizes, with some constraints, so, for full portability, you ought to pass the element size to the sort routine. – Eric Postpischil Jan 25 '23 at 13:58
  • @EricPostpischil Would passing `array` and `k` as distinct parameters to the type-specific routine and doing the comparaison there after casting solve the problem? – Mehdi Charife Jan 25 '23 at 14:01
  • 1
    @MehdiCharife: Yes, if done correctly. – Eric Postpischil Jan 25 '23 at 14:02
  • @EricPostpischil I tried it and it seems to work. Thanks. – Mehdi Charife Jan 25 '23 at 14:07

3 Answers3

1

Type void is an incomplete type. So used by you the expression array + k with the pointer arithmetic in the if statement

 if(equals(array + k, pelement)) {

is invalid.

Also you need to pass to the function the size of objects stored in the array that will be used in expressions with the pointer arithmetic.

Using your approach the function should be declared similarly to standard C function bsearch that looks like

void *bsearch(const void *key, const void *base,
              size_t nmemb, size_t size,
              int (*compar)(const void *, const void *));

Only the return type must be changed from void * to int.

That is the declaration pf your function will look like

int is_in( const void *pvalue, 
           const void *array, 
           size_t nmemb, 
           size_t size, 
           int cmp( const void *, const void *) );

The function can be defined the following way

int is_in( const void *pvalue, 
           const void *array, 
           size_t nmemb, 
           size_t size, 
           int cmp( const void *, const void *) )
{
    size_t i = 0;
    
    while ( i < nmemb && cmp( pvalue, ( const char * )array + i * size  )   != 0 ) i++;

    return i != nmemb;
}

In general the comparison function shall return an integer less than, equal to, or greater than zero if the searched element is considered, respectively, to be less than, to match, or to be greater than the array element.

In your case as you have an array of pointers that can point to arbitrary objects then the function should return 0 if elements passed to the function are equal each other or just a positive value if they are unequal each other.

int ptreq( const void *ptr1, const void *ptr2 ) 
{
    return *( const Symbol ** )ptr1 != *( const Symbol ** )ptr2;
}

Pay attention to that the passed searched elementmust have the typeSymbol **`.

Here is a demonstration program.

#include <stdio.h>

int is_in( const void *pvalue,
    const void *array,
    size_t nmemb,
    size_t size,
    int cmp( const void *, const void * ) )
{
    size_t i = 0;

    while (i < nmemb && cmp( pvalue, ( const char * )array + i * size ) != 0) i++;

    return i != nmemb;
}

int cmp_ptr( const void *ptr1, const void *ptr2 )
{
    return *( const int ** )ptr1 != *( const int ** )ptr2;
}

int main( void )
{
    int x, y, z;

    int * a[] = { &x, &y, &z };
    const size_t N = sizeof( a ) / sizeof( *a );

    int *pvalue = &y;

    printf( "&y is in the array = %s\n",
        is_in( &pvalue, a, N, sizeof( *a ), cmp_ptr ) ? "true" : "false" );

    int v;
    pvalue = &v;

    printf( "&v is in the array = %s\n",
        is_in( &pvalue, a, N, sizeof( *a ), cmp_ptr ) ? "true" : "false" );
}

The program output is

&y is in the array = true
&v is in the array = false
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
1

A Symbol** passed to a void* parameter doesn't come out as an array in the other end unless you cast it to a proper type. array + k is invalid C and will not compile cleanly on conforming compilers. You cannot do pointer arithmetic on void* nor can you iterate through what it points at without knowing the item size - there's a reason why qsort takes that as parameter.

A correctly written standard C function might look something like this:

#include <stddef.h>
#include <stdbool.h>

bool is_in (const void* array, 
            size_t      n_items,
            size_t      item_size,
            const void* element,
            int (*equals)(const void*, const void*))
{
    unsigned char* byte = array;
    for(size_t i=0; i<n_items; i++)
    {
       if(equals(&byte[i*item_size], element))
       {
         return true;
       }
    }

    return false;
}

int symbol_equal (const void* obj1, const void* obj2)
{
  const Symbol* s1 = obj1;
  const Symbol* s2 = obj2;
  ...
  // in case you passed an array of pointers, then an extra level of dereferencing here
}
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • @Lundin Did you write `symbol_equals` with the intention to compare two symbol pointers? If so, shouldn't `&byte[i*item_size]` be `byte[i*item_size]`, as `byte` is already an array of pointers? If not, shouldn't `const Symbol* s1` be `const Symbol** ps1` in stead? – Mehdi Charife Jan 25 '23 at 14:32
  • 1
    @MehdiCharife It's intentionally left vague since it depends on if you use this function in a sensible way (passing a pointer to an array of data) or an extra complicated way (passing a pointer to an array of pointers). The callback has to be adapted to suit the data on the caller side. – Lundin Jan 25 '23 at 14:34
0

As others have pointed out, the problem results from trying to perform addition on void*, which is not defined in standard C. While other answers avoid this by passing the item size, as is the case with qsort(), I managed to solve the problem by separating array and k into distinct parameters of the equals() routine, which then performs pointer arithmetic after casting into the proper, non-void* type.

int is_in(void* list, int size, void* pelement, int (*equals)(void* this, int k, void* that)) {
    for(int k = 0; k < size; k++) {
        if(equals(list, k, pelement)) {
            return 1;
        }
    }
    return 0;
}
int ptreq(void* ptr1, int k, void* ptr2) {
    return (*(((Symbol**) ptr1) + k) == (Symbol*) ptr2);
}
Mehdi Charife
  • 722
  • 1
  • 7
  • 22