user2741736
user2741736

Reputation: 23

Function Pointers in C and their behaviour

I was experimenting with c and function pointers. following piece of code works fine with gcc as compiler.

typedef int( * one_var_func)(int);

int Multiply(int x, int y) {
  return x * y;
}

one_var_func curry(int( * f)(int, int), int x) {
  int curried_f(int y) {
    return f(x, y);
  }
  return (curried_f);
}

int apply(int( * f)(int), int x) {
  return f(x);
}

int main() {
  int( * p)(int, int);
  one_var_func q;
  int e;
  p = & Multiply;
  q = curry(p, 2);
  e = apply( * q, 10);
  printf("%d \n", e);
  return 1;
}

However when i make this minor modification;

int apply(int (*f)(int) ,int x){
  int a;
  a=f(x)
  return a;
}

Program throws a segmentation fault. I dont understand why and how. An explanation would be really nice.

Upvotes: 1

Views: 207

Answers (5)

alinsoar
alinsoar

Reputation: 15793

You declared the type alias one_var_func to represent a pointer to a function int->int. You can also declare it as function, without pointer, and use pointer to instance of this typename.

First, you are not allowed to define a function using type aliases. This is a syntax error.

The identifier declared in a function definition (which is the name of the function) shall have a function type, as specified by the declarator portion of the function definition. 138)

I.e. a function definition is allowed to look only so:

function-definition:
  declaration-specifiers declarator  declaration-listopt compound-statement

The fact that gcc accepted probably with a warning only, this is gcc extension.

q = curry(p, 2);

What you wrote is not C, you know some functional programming and want to apply it in C, but in C the same concepts like curry are applied differently as there is no concept of closure and the local bindings of a function that returns another function and encloses the locals of the first are not in the kernel of the C language, you need to encode them yourself.

There is no direct way for q to access the local binding 2.

e = apply( * q, 10);

The same, to evaluate q applied on 10 you need some local environments that should be encoded inside q.

There are other things to say about your code, before you try to implement evaluators of lisp in C I suggest you first to learn C and after that to read about the implementations of lisp starting from here, as they are not self evident.

Upvotes: 0

amitkindre
amitkindre

Reputation: 43

when function

one_var_func curry(int( * f)(int, int), int x) 
{
  int curried_f(int y) 
  {
    return f(x, y);  //f and x are local to curry 
  }
  return (curried_f);
}

returns it's local variables f and x are no longer on stack.

when function curried_f

int curried_f(int y) 
{
    return f(x, y);  //f and x are local to curry 
}

is called it tries to acess x and f, which causes segmentation fault.

Upvotes: 0

user2371524
user2371524

Reputation:

C doesn't have a concept of closures which makes it impossible to implement currying based on plain function pointers. What you're trying here is using a closure:

one_var_func curry(int( * f)(int, int), int x) {
  int curried_f(int y) {
    return f(x, y);
  }
  return (curried_f);
}

This would mean that the nested function "captures" the value of x. But in C, any variable with automatic storage duration doesn't exist any more once the execution leaves the enclosing scope, and there's no concept of closures that could prevent that.

Given that even a nested function doesn't exist in C, although GCC supports it as an extension, if you really need to apply currying, you have to define your own "function object". An example in standard C could look like this:

#include <stdio.h>
#include <stdlib.h>

typedef struct intfunc
{
    int (*f)();
    void *ctx;
} intfunc;

typedef struct curryctx
{
    int (*f)();
    int x;
} curryctx;

static int currycall(void *ctx, int x)
{
    curryctx *cctx = ctx;
    return cctx->f(cctx->x, x);
}

int intfunc_call(intfunc f, int x)
{
    return f.ctx ? f.f(f.ctx, x) : f.f(x);
}

intfunc createfunc(int (*f)())
{
    return (intfunc){f, 0};
}

intfunc curryfunc(int (*f)(), int x)
{
    curryctx *cctx = malloc(sizeof *cctx);
    if (!cctx) exit(1);
    cctx->f = f;
    cctx->x = x;
    return (intfunc){currycall, cctx};
}

static int multiply(int x, int y)
{
    return x*y;
}

int main()
{
    intfunc multiply_by_two = curryfunc(multiply, 2);
    printf("%d\n", intfunc_call(multiply_by_two, 10));
    free(multiply_by_two.ctx);
    return 0;
}

Of course, this gets complex quite quickly, so I suggest you better forget about that idea altogether.

Upvotes: 1

Tom Karzes
Tom Karzes

Reputation: 24052

Nested functions are a gcc extension. The gcc documentation states that, once the containing function invocation has exited, any pointers to the nested function become invalid, at least if they attempt any up-level variable references.

This makes sense, because as long as the containing function is active, its local variables remain allocated and the up-level references from the nested function can be resolved. But once the containing function exits, it would need to support closures to preserve the stack frame, which it doesn't.

Upvotes: 2

sepp2k
sepp2k

Reputation: 370122

Nested functions are a GCC extension that does not exist in standard C, so this answer (like the question) is GCC-specific.

Nested functions in C do not provide closures. That is, a nested function can only access local variables of the outer function until the outer function returns. The GCC documentation has the following to say on this subject:

If you try to call the nested function through its address after the containing function exits, all hell breaks loose. If you try to call it after a containing scope level exits, and if it refers to some of the variables that are no longer in scope, you may be lucky, but it’s not wise to take the risk. If, however, the nested function does not refer to anything that has gone out of scope, you should be safe.

Both versions of your code violate this rule, so why does only one cause a segfault? One answer is that, just like "undefined behaviour" "all hell breaks loose" can describe all types of behaviour, including seemingly working as expected.

The more implementation-oriented answer is that returning from a function does not actually erase its contents on the stack immediately - the values just stay there until another function overrides them when they need the stack space. Introducing a new local variable makes the function need more stack space, so your second function overrides stack memory that previous version did not.

Upvotes: 4

Related Questions