0

In the following code snippet Reference, compare is called from main() without any parameters being passed. I assume it is taking ((char *)&key, (char *)string) as the two parameters needed for the function call. But how does it work internally in C? Does the compiler fill in the arguments when compare is called?

#include <search.h>
#include <string.h>
#include <stdio.h>

#define  CNT           2

int compare(const void *arg1,const void *arg2)
{
   return (strncmp(*(char **)arg1, *(char **)arg2, strlen(*(char **)arg1)));
}

int main(void)
{
   char **result;
   char *key = "PATH";
   unsigned int num = CNT;
   char *string[CNT] =  {
      "PATH = d:\\david\\matthew\\heather\\ed\\simon","LIB = PATH\\abc" };

   /* The following statement finds the argument that starts with "PATH"         */

   if ((result = (char **)lfind((char *)&key, (char *)string, &num,
                  sizeof(char *), compare)) != NULL)
      printf("%s found\n", *result);
   else
      printf("PATH not found \n");
   return 0;


}

How do function pointers in C work?

Community
  • 1
  • 1
jaamit
  • 393
  • 3
  • 11
  • 1
    Note that if the comparison function is passed (pointers to) the strings `"F"` and `"Finnegan's Wake"`, these will compare equal, but if the strings are passed in the reverse order, they will compare unequal. In general, this makes comparisons unreliable. However, [`bsearch()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/bsearch.html) passes the key as the first argument and the table entry as the second argument. The specification for [`lfind()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/lfind.html) does not make that promise, though. – Jonathan Leffler Apr 26 '15 at 06:01
  • Yes, I understand the limitations of lfind() but I just used it as an example for understanding. – jaamit Apr 26 '15 at 07:58

4 Answers4

3

compare is called from main() without any parameters being passed.

No. It is just referred to from main.

Essentially, it tells lfind "Hey, if you want to compare entries, take this function - it can be called exactly the way you expect it."

lfind, then, knows about that and whenever it needs to compare two entries, it puts the arguments wherever it would put them on a normal call (on the stack, into the right registers or wherever, depending on the calling conventions of the architecture) and performs the call to the given address. This is called an indirect call and is usually a bit more expensive than a direct call.

Let's switch to a simpler example:

#include <stdio.h>

int add1(int x) {
    return x + 1;
}

int times2(int x) {
    return x * 2;
}

int indirect42(int(*f)(int)) {
    // Call the function we are passed with 42 and return the result.
    return f(42);
}

int indirect0(int(*f)(int)) {
    // Call the function we are passed with 0 and return the result.
    return f(0);
}

int main() {
    printf("%d\n", add1(33));
    printf("%d\n", indirect42(add1));
    printf("%d\n", indirect0(add1));

    printf("%d\n", times2(33));
    printf("%d\n", indirect42(times2));
    printf("%d\n", indirect0(times2));
    return 0;
}

Here, I call the function add1() on my own first, then I tell two other functions to use that function for their own purpose. Then I do the same with another function.

And this works perfectly - both functions are called with 33 by me, then with 42 and with 0. And the results - 34, 43 and 1 for the first and 66, 84 and 0 for the 2nd function - match the expectations.

If we have a look at the x86 assembler output, we see

…
indirect42:
.LFB13:
    .cfi_startproc
    movl    4(%esp), %eax
    movl    $42, 4(%esp)
    jmp *%eax
    .cfi_endproc

The function gets the given function pointer into %eax, then puts the 42 where it is expected and then calls %eax. (Resp., as I activated optimization, it jumps there. The logic remains the same.)

The function doesn't know which function is to be called, but how it is to be called.

indirect0:
.LFB14:
    .cfi_startproc
    movl    4(%esp), %eax
    movl    $0, 4(%esp)
    jmp *%eax
    .cfi_endproc

The function does the same as the other one, but it passes 0 to whatever function it gets.

Then, as it comes to calling all this stuff, we get

    movl    $33, (%esp)
    call    add1
    movl    %eax, 4(%esp)
    movl    $.LC0, (%esp)
    call    printf

Here we have a normal function call.

    movl    $add1, (%esp)
    call    indirect42
    movl    %eax, 4(%esp)
    movl    $.LC0, (%esp)
    call    printf

Here, the address of add1 is pushed to the stack and given to indirect42 so that function can do with it as it wants.

    movl    $add1, (%esp)
    call    indirect0
    movl    %eax, 4(%esp)
    movl    $.LC0, (%esp)
    call    printf

The same with indirect0.

(other stuff snipped as it works the same)

glglgl
  • 89,107
  • 13
  • 149
  • 217
  • 1
    This is the only answer (so far) to explicitly point out the erroneous assertion in the question. – Jonathan Leffler Apr 26 '15 at 06:02
  • 1. This implies compare will use the parameters from stack that were pushed before compare is called (normal function call case). Which in turn implies that lsearch 1st 2 params need to match the compare func? – jaamit Apr 26 '15 at 08:08
  • @jaamit No. It is `lsearch`'s responsibility to select the right arguments for the called function's parameters. I'll expand on my answer with an example. – glglgl Apr 26 '15 at 08:14
1

There's no magic. You're not actually calling it from main, you're just passing along a reference to it when you call lfind. The compiler lets you do this because it already knows (a) what kind of function is expected there (ie, with those two args) and (b) knows that the one you're using matches correctly, so no complaints. If you'd defined compare differently (wrongly), it wouldn't have compiled.

Inside of lfind, it's invoked directly with the two parameters by name. Function pointers like this seem slippery when you first see them, but in C they're actually laboriously explicit-- you must pass in a reference to a function that matches the function signature declared in lfind's declaration. And inside of lfind, that code must call the passed-in function in the defined way.

Ben Zotto
  • 70,108
  • 23
  • 141
  • 204
  • How does `compare` know `(char *)&key, (char *)string` I need these parameters? Is it because of matching parameter types? – jaamit Apr 26 '15 at 08:17
0

Function pointers work exactly the same as regular function calls, by pushing the parameters on the stack and making a subroutine call. The only difference is the location of the subroutine call, with a function pointer, the location is loaded from the given variable whereas with a direct function call, the location is statically generated by the compiler as an offset from wherever the code block is loaded to.

The best way to understand this is actually using GDB and look at the instructions emitted by the compiler. Try it with a less complex code example, then work up from there. You will save time in the long run.

Brian Topping
  • 3,235
  • 28
  • 33
0

Syntax for lfind is

void *lfind (const void *key, const void *base, size_t *nmemb, size_t size, comparison_fn_t compar)  

The lfind function searches in the array with *nmemb elements of size bytes pointed to by base for an element which matches the one pointed to by key. The function pointed to by compar is used decide whether two elements match.

Pointer to compar function is passed to lfind. lfind will then call compar internally using base and key as arg1 and arg2, respectively.

GNU Libc defines comparison_fn_t in stdlib.h if _GNU_SOURCE set.

#include <stdlib.h>

#ifndef HAVE_COMPARISON_FN_T
typedef int (*comparison_fn_t)(const void *, const void *);
#endif
haccks
  • 104,019
  • 25
  • 176
  • 264