0

I am trying to write a generic C function, that returns minimum of an array using function pointers.

The code is :

#include <stdio.h>

int min_index(void *arr, int size, int (*compare)(void *, void *))
{
    int i;
    int min = 0;

    for (i = 1; i < size; i++)
    {
        if (((*compare)(&arr[i], &arr[min])) < 0)
            min = i;
    }

    return min;
}

int comp_int(int *arr1, int *arr2)
{
    if (*arr1 < *arr2)
        return -1;
    else
        return 1;
}

int comp_char(char *arr1, char *arr2)
{
    if (*arr1 < *arr2)
        return -1;
    else
        return 1;
}

int main()
{
    int arr1[5] = {45, 56, 34, 23, 19};
    int index = min_index(arr1, 5, comp_int);  // calling min_index with integer array
    printf("Minimum value in the array is %d\n", arr1[index]);

    char arr2[5] = {'a', 'b', 'c', 'd', 'e'};
    int index2 = min_index(arr2, 5, comp_char); // calling min_index with char array
    printf("Minimum value in the array is %c\n", arr2[index2]);

    return 0;
}
  1. This code worked for only char arrays. It did not work for int arrays.

  2. I debugged the code. I found that, when I call min_index function with the integer array, only first value of arr is correct number, but remaining are some garbage values.

  3. But, when I call min_index function with character array, all the values are correct. There are no garbage values in this case. I got correct output.

Question : Why does the code work for char arrays not for int arrays ?

Krishna Kanth Yenumula
  • 2,533
  • 2
  • 14
  • 26
  • One major problem is that your comparison functions doesn't really follow the contract you set out for comparison function in the `min_index` function. The `comp_int` and `comp_char` really needs to use the exact same signature as expected by `min_index`. Cast the pointers as needed inside the functions. – Some programmer dude Jun 18 '21 at 18:53
  • 1
    You should also pass the size of the each element in the array, because `sizeof(char)` is probably different from `sizeof(int)`. – Bob__ Jun 18 '21 at 18:54
  • 1
    Another problem is that dereferencing a `void *` (like you do with `arr[i]` in the `min_index` function) is not possible. Try to find an implementation of the standard C [`qsort`](https://en.cppreference.com/w/c/algorithm/qsort) function to see how it handles these things. – Some programmer dude Jun 18 '21 at 18:55
  • @Someprogrammerdude But, this code worked for `char` arrays. – Krishna Kanth Yenumula Jun 18 '21 at 18:56
  • 2
    First, this code should puke warnings. If it doesn't do so in your configuration you need to turn up your warning levels. Second, the vernacular is completely wrong. Consider this: how big is a `void` ? Don't work too long on that; there is no definitive standard-based answer. There is a reason functions like `qsort` (which uses a comparator model similar to what you're doing here) utilizes *both* an element count *and* an element *size*. – WhozCraig Jun 18 '21 at 18:57
  • 2
    Also, consider this: https://stackoverflow.com/questions/3523145/pointer-arithmetic-for-void-pointer-in-c and: https://stackoverflow.com/questions/15468441/dereference-void-pointer/25531099 – Bob__ Jun 18 '21 at 18:57
  • 1
    @KrishnaKanthYenumula "worked" is a relative term. Pointer arithmetic is impossible with base `void*` because the underlying type (`void`) has no width stride. Some compilers "help" by treating `void*` as `char*` when performing pointer arithmetic. I say "helped" figuratively, since the end it only leads to confusion, this case for example, and isn't standard-backed. – WhozCraig Jun 18 '21 at 19:00
  • @WhozCraig "In GNU C, addition and subtraction operations are supported on pointers to void and on pointers to functions. This is done by treating the size of a void or of a function as 1." – Krishna Kanth Yenumula Jun 18 '21 at 19:01
  • @WhozCraig Link is : https://gcc.gnu.org/onlinedocs/gcc-4.8.0/gcc/Pointer-Arith.html – Krishna Kanth Yenumula Jun 18 '21 at 19:01
  • The problem is that you need to specify the size of each element in the array. Otherwise you can't correctly index into it. Look at the man page for `qsort`, which has the exact same requirements. Your function prototype should be identical, except for the return type. – Tom Karzes Jun 18 '21 at 19:01
  • 2
    @KrishnaKanthYenumula And as I said in my followup comment, was that non-standard implementation ultimately helpful in this case, or rather, did it lead you down a rabbit hole? It would have been far better had they just puked an error on deference or pointer-arithmetic against a void pointer from inception. At least then you would have been pushed in the right direction by the compiler rather than being led astray by it. – WhozCraig Jun 18 '21 at 19:03
  • @WhozCraig I understood your point. Thank you. – Krishna Kanth Yenumula Jun 18 '21 at 19:06
  • @WhozCraig I came up with solution, based on your comments. Please take a look at the answer. – Krishna Kanth Yenumula Jun 19 '21 at 03:28

2 Answers2

1

It doesn't work because you need to cast the array itself on the original type and then access by index. Because C does not have template or a type check you will need to access the min_of() like functions by the size and the sign to get the correct conversion and sorting.

#include <stdio.h>

#define INVALID_VAL_TYPE  (int)(~0)

int new_umin_index(void * arr, int arr_len, int vsize){
    int i, index = 0;

    switch (vsize) {

        case sizeof(unsigned char):
            for (i = 0; i < arr_len; i++) {
                if ( ((unsigned char *)arr)[i] < ((unsigned char *)arr)[index] ){
                    index = i;
                }
            }
            break;

        case sizeof(unsigned):
            for (i = 0; i < arr_len; i++) {
                if ( ((unsigned *)arr)[i] < ((unsigned *)arr)[index] ){
                    index = i;
                }
            }
            break;
    
        default:
            index = INVALID_VAL_TYPE;
            break;
    }
    return index;
}

int new_imin_index(void * arr, int arr_len, int vsize){
    int i, index = 0;

    switch (vsize) {
        
        case sizeof(signed char):
            for (i = 0; i < arr_len; i++) {
                if ( ((signed char *)arr)[i] < ((signed char *)arr)[index] ){
                    index = i;
                }
            }
            break;

        case sizeof(int):
            for (i = 0; i < arr_len; i++) {
                if ( ((int *)arr)[i] < ((int *)arr)[index] ){
                    index = i;
                }
            }
            break;
    
        default:
            index = INVALID_VAL_TYPE;
            break;
    }
    return index;
}

int main(void) {

    int arr1[5] = {45, 56, 34, 23, 19};
    int i_min_index = new_imin_index(&arr1, 5, sizeof(int));
    printf("Minimum value in array<int> is %i \n", arr1[i_min_index]);

    char arr2[5] = {'b', 'a', 'c', 'd', 'e'};
    int c_min_index = new_umin_index(&arr2, 5, sizeof(char));
    printf("Minimum value in array<char> is %c \n", arr2[c_min_index]);

    return 0;
}
SrPanda
  • 854
  • 1
  • 5
  • 9
1

I came up with a solution, based on the comments of several users. This time I enabled all the warnings, and took care of every warning. The code is :

#include <stdio.h>
void* minimum(void *arr, int arr_count, size_t size, int (*compare)(const void *, const void *))
{
    void* pmin = arr;

    for(int i=1; i<arr_count; i++)
    {
        void* value = (char*)arr + i*size;  // size is the sizeof the type.

        if(((*compare)(value, pmin))<0)
        pmin = value;

    }

    return pmin;
    
}

int compare_ints(const void* a, const void* b)
{
    int arg1 = *(const int*)a;
    int arg2 = *(const int*)b;
 
    if (arg1 < arg2) return -1;
    if (arg1 > arg2) return 1;
    return 0;
}

int compare_chars(const void* a, const void* b)
{
    char arg1 = *(const char*)a;
    char arg2 = *(const char*)b;
 
    if (arg1 < arg2) return -1;
    if (arg1 > arg2) return 1;
    return 0;
}


int main()
{
    int arr1[5] = {45, 56, 34, 23, 19};
    int *pmin = minimum(arr1, 5,4, compare_ints); // In my system sizeof(int) is 4
    printf("Minimum value in the array is %d\n", *pmin);

    char arr2[5] = {'a', 'b', 'c', 'd', 'e'};
    char* pmin2 = minimum(arr2, 5,1, compare_chars); // sizeof(char) is 1
    printf("Minimum value in the array is %c\n",*pmin2 );

    return 0;
}

The output is (without any warnings) :

Minimum value in the array is 19
Minimum value in the array is a
Krishna Kanth Yenumula
  • 2,533
  • 2
  • 14
  • 26