12

How (in GCC/"GNU C") do you declare a function pointer which points to an __attribute__((const)) function? The idea being that I want the compiler to avoid generating multiple calls to the function called through the function pointer when it can cache the return value from a previous call.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Never have done that and almost falling asleep to research, but try wrapping a call by address of such a function with explicitly declared function that has const attribute and accepts that pointer as a parameter. If gcc can determine that pointer address itself & arguments are not changing - it should eliminate unnecessary calls. –  Feb 25 '12 at 04:37
  • 1
    @Vlad: I thought of that too, but then gcc refuses to inline the function in cases where I want it to. Originally I had a wrapper function like that, but I removed it to fix the inlining behavior. In case it's interesting, the function in question is `((pthread_t (*)(void))0xffff0fe0)` (the Linux-ARM get-thread-pointer function). – R.. GitHub STOP HELPING ICE Feb 25 '12 at 04:41
  • Interesting question. Did justin's answer have the desired result? – Praxeolitic Dec 11 '16 at 05:33

3 Answers3

6
typedef void (*t_const_function)(void) __attribute__((const));

static __attribute__((const)) void A(void) {
}

static void B(void) {
}

int main(int argc, const char* argv[]) {
    t_const_function a = A;

    // warning: initialization makes qualified
    // function pointer from unqualified:
    t_const_function b = B;

    return 0;
}

Or just:

__attribute__((const)) void(*a)(void) = A;
justin
  • 104,054
  • 14
  • 179
  • 226
  • Bleh, `typedef` is always the solution when you have a nasty function pointer type. Accepted. But any idea if there's a way to write the cast in my comment on the main question without a `typedef`? – R.. GitHub STOP HELPING ICE Feb 25 '12 at 07:49
  • @R. Unfortunately, I do *not* know how to wind it all into one statement without a `typedef` -- it doesn't appear to be possible on GCC 4.2. `((__attribute__((const)) pthread_t(*)(void))0xffff0fe0)` is how I think it would be done, but it's interpreted different from what you want (it appears GCC applies the attribute to the return type, not the function). – justin Feb 25 '12 at 08:27
2

GCC 12.2.1 treats __attribute__((pure)) and __attribute__((const)) subtly differently. The former applies only to function declarations, never to types, while the latter actually can be applied both to function declarations and to pointer-to-function types (but not to function types!).

/* Function declarations */

int const_fn(int) __attribute__((const)); // OK
int pure_fn(int) __attribute__((pure)); // OK


/* Function type definitions */

typedef int const_fn0_t(int) __attribute__((const)); // warning: 'const' attribute ignored
typedef int pure_fn0_t(int) __attribute__((pure)); // warning: 'pure' attribute ignored

typedef int __attribute__((const)) const_fn1_t(int); // warning: 'const' attribute ignored
typedef int __attribute__((pure)) pure_fn1_t(int); // warning: 'pure' attribute ignored

typedef __attribute__((const)) int const_fn2_t(int); // warning: 'const' attribute ignored
typedef __attribute__((pure)) int pure_fn2_t(int); // warning: 'pure' attribute ignored

__attribute__((const)) typedef int const_fn3_t(int); // warning: 'const' attribute ignored
__attribute__((pure)) typedef int pure_fn3_t(int); // warning: 'pure' attribute ignored


/* Pointer-to-function type definitions */

typedef int (*const_fn0_ptr_t)(int) __attribute__((const)); // OK
typedef int (*pure_fn0_ptr_t)(int) __attribute__((pure)); // warning: 'pure' attribute ignored

typedef int (* __attribute__((const)) const_fn1_ptr_t)(int); // OK
typedef int (* __attribute__((pure)) pure_fn1_ptr_t)(int); // warning: 'pure' attribute ignored

typedef int __attribute__((const)) (*const_fn2_ptr_t)(int); // OK
typedef int __attribute__((pure)) (*pure_fn2_ptr_t)(int); // warning: 'pure' attribute ignored

typedef __attribute__((const)) int (*const_fn3_ptr_t)(int); // OK
typedef __attribute__((pure)) int (*pure_fn3_ptr_t)(int); // warning: 'pure' attribute ignored

__attribute__((const)) typedef int (*const_fn4_ptr_t)(int); // OK
__attribute__((pure)) typedef int (*pure_fn4_ptr_t)(int); // warning: 'pure' attribute ignored


void demo(const_fn0_t *pcf0,
          const_fn1_t *pcf1,
          const_fn2_t *pcf2,
          const_fn3_t *pcf3,
          pure_fn0_t *ppf0,
          pure_fn1_t *ppf1,
          pure_fn2_t *ppf2,
          pure_fn3_t *ppf3,
          const_fn0_ptr_t cfp0,
          const_fn1_ptr_t cfp1,
          const_fn2_ptr_t cfp2,
          const_fn3_ptr_t cfp3,
          const_fn4_ptr_t cfp4,
          pure_fn0_ptr_t pfp0,
          pure_fn1_ptr_t pfp1,
          pure_fn2_ptr_t pfp2,
          pure_fn3_ptr_t pfp3,
          pure_fn4_ptr_t pfp4)
{
    /* calling functions directly */
    const_fn(0); // warning: statement with no effect
    pure_fn(0); // warning: statement with no effect

    /* calling through pointers to function types */
    pcf0(0); // no warning
    pcf1(0); // no warning
    pcf2(0); // no warning
    pcf3(0); // no warning
    ppf0(0); // no warning
    ppf1(0); // no warning
    ppf2(0); // no warning
    ppf3(0); // no warning

    /* calling through pointer-to-function types */
    cfp0(0); // warning: statement with no effect
    cfp1(0); // warning: statement with no effect
    cfp2(0); // warning: statement with no effect
    cfp3(0); // warning: statement with no effect
    cfp4(0); // warning: statement with no effect
    pfp0(0); // no warning
    pfp1(0); // no warning
    pfp2(0); // no warning
    pfp3(0); // no warning
    pfp4(0); // no warning
}

You actually can attach the attribute to multiple levels of pointer indirection:

typedef int (*const_fn_ptr_t)(int) __attribute__((const));

int demo1(const_fn_ptr_t (*pcf)(void) __attribute__((const))) {
    pcf(); // warning: statement with no effect
    pcf()(0); // warning: statement with no effect

    const_fn_ptr_t cfp = pcf(); // no warning
    cfp(0); // warning: statement with no effect
    return cfp(0); // no warning
}

The demo1 function above takes one argument, which is of type “pointer to a function that takes no arguments, is constant, and returns a pointer to a function that takes an integer argument, is constant, and returns an integer.”

All of the preceding leads up to an answer to OP's question. You can declare a pointer to a constant function (but cannot declare a pointer a pure function) thusly:

int (*pf)(void *) __attribute__((const));

In a function parameter list, it might look like:

void sort(void *array[],
          size_t array_size,
          int (*compare)(const void *, const void *) __attribute__((const)))
{
    compare(array[0], array[1]); // warning: statement with no effect
}

Unfortunately, I have found no way to attach __attribute__((const)) to a pointer-to-function type outside of a declaration. In particular, it does not work in a cast:

void demo2(int (*pf)(int)) {
    ((int (* __attribute((const)))(int)) pf)(0); // warning: 'const' attribute does not apply to types
}

However, you can cast a pointer-to-function to a typedef'd pointer-to-constant-function type:

void demo3(int (*pf)(int)) {
    pf(0); // no warning
    typedef int (*cfp_t)(int) __attribute__((const));
    ((cfp_t) pf)(0); // warning: statement with no effect
}

If any GCC developer is reading this, please fix __attribute__((pure)) so it has the same applicability as __attribute__((const)).

Matt Whitlock
  • 756
  • 7
  • 10
0

Although this is not quite the answer to your question, you probably want to know this:

You can't in the general case expect the compiler to perform the optimization you expect here. The compiler cannot in the general case do the alias analysis necessary to know that multiple uses of a function pointer correspond to the same function.

A function call in between two invocations of the function through the pointer could, in the general case, alter the pointer contents, thus causing the invoked function to be different in the second call.

Because of the nature of C, doing proper alias analysis is often intractable, and this sort of optimization is thus not likely to happen.

Perry
  • 4,363
  • 1
  • 17
  • 20
  • 2
    That is what __attribute__((const)) does - it tells the compiler that you know better and gives a green light for certain optimizations. –  Feb 25 '12 at 04:39
  • In my case it can know, though, because the pointer is an address literal (an integer cast to a function pointer). See the comments. – R.. GitHub STOP HELPING ICE Feb 25 '12 at 04:41
  • 2
    @Vlad: I think Perry's point was that even if the pointed-to function is `const`, the compiler would also have to be sure that the *pointer* did not change between invocations. But that's not too hard to determine, and in my case it's not possible since it's a literal absolute address. – R.. GitHub STOP HELPING ICE Feb 25 '12 at 04:42
  • The compiler can handle declaring a function attribute const no problem. The issue is when you start indirecting through function pointers. The literal name of a function is in effect a constant pointer and the compiler can know it isn't altered -- but a variable that contains a pointer to an attribute const function is different. – Perry Feb 25 '12 at 04:43
  • @R..: it actually is surprisingly hard to determine in the general case. This is why C99 added keywords to indicate pointers would not be aliased in function calls for example. It is a giant mess. Even though you're using a literal absolute address, I suspect the compiler doesn't know how to optimize that since there are so few cases where it can... – Perry Feb 25 '12 at 04:46
  • If `func` is a function pointer and it is never assigned-to between multiple calls to `func()`, and either `&func` has never been taken or no writes through character pointers have taken place between the calls to `func()`, then it's trivial for the compiler to know the same function is called each time. – R.. GitHub STOP HELPING ICE Feb 25 '12 at 04:48
  • In any case, see the comment on the main question. While a function pointer type is being used, there is no function pointer variable in play. Just a literal absolute address cast to a function pointer type. – R.. GitHub STOP HELPING ICE Feb 25 '12 at 04:48
  • As an alternative, are you in a position to save the output of the first call to the function on your own? – Perry Feb 25 '12 at 04:50
  • (BTW, you say "no writes to pointers occur between the calls to func()", but the problem is, any function called between the two invocations could set any globally accessible data structure. If there are no calls to other functions in between the two calls to func() and no writes to any pointer in between, yes, you are okay, but typical code is messier.) – Perry Feb 25 '12 at 04:53
  • Saving the result myself is always an option, but it results in a significant amount of duplication of idiomatic code. This is true about any use of `__attribute__((const))`. As for your second comment, in a very common case (the most common?) the function pointer is a local automatic variable whose address is never taken, in which case it's trivial to determine that it wasn't modified. – R.. GitHub STOP HELPING ICE Feb 25 '12 at 05:03
  • 1
    If you know that the pointer to func would never change the easiest would probably be to declare it `register` as you say, but also to declare the pointer itself `const` qualified. – Jens Gustedt Feb 25 '12 at 07:58