function pointers

K

Keith Thompson

6.8.4.2 of n1124.pdf (C99 draft)


Function pointers are pointers and not integers.

The OP's question was not well stated, but I don't believe you've
answered it.

Storing one of several values in a function pointer object, so you can
call a function that's determined at run time, can substitute for a
switch statement, which executes one of several blocks of code
selected at run time. There are significant differences, of course,
and the two are not directly interchangeable. For example, the
function pointer approach provides no equivalent for a switch
statement's fallthrough semantics.
 
T

Tomás Ó hÉilidhe

how the function poiners avoids the usage of switch cases?

Here's a typical switch statement:

switch (user_input)
{
case 0: DeleteFile(); break;
case 1: CopyFile(); break;
case 2: RenameFile(); break;
case 4: MoveFile();
}

And here's the function pointer equivalent:

void (*const funcs[4])(void) = {DeleteFile,CopyFile,Renamefile,MoveFile};

funcs[user_input];
 
S

spaglia

how the function poiners avoids the usage of switch cases?

Instead of using a switch statement, you can use an array of function
pointers and execute a function call using an integer index.
 
R

Richard Harter

how the function poiners avoids the usage of switch cases?

This isn't quite the right newsgroup (some group about comp sci
would be) but it can be answered in the context of C. As part of
my answer I'm going to do a riff on the theory of flow control
structures. They provide context for your question.

There are three levels of simple transfer commands aka gotos.
They are:

(1) The simple goto. The statement has the command verb and a
fixed target label. It transfers control to the code marked by
the target label.

(2) The computed goto, aka switch, select, or caseof, depending
on the language. In C it is the switch. The essence of the
computed goto is a fixed list of (value,label) pairs and an
expression that can yield one of the values in the list. Control
is transferred to the label corresponding to the value of the
expression.

The computed goto can either be structured or unstructured. In
the unstructured form the labels can be anywhere within the
source code unit. In the structured form there is a block
associated the computed goto and the labels are all within the
block. The C switch statement is a structured computed goto,
albeit with some caveats being structured.

(3) The variable goto. The statement has a command verb and a
label variable. It transfers control to the label that is the
value of the label variable. Standard C does not have the
variable goto, although gcc (and I presume other compilers) offer
variants of it as a standard extension.

It can be shown that the computed goto/switch is a stronger flow
control construct than the goto, and that the variable goto is
stronger than the computed goto, where strength means that more
compact and more efficient code can be written user the stronger
construct.

A function invocation is a variable goto with the caveat that
control must be returned to the calling function after the called
function. In C the code within the called function has a
different scope than the scope within the calling function. This
is also true in most other languages although there are many
caveats.

Now what does this have to do with function pointers and switch
cases? Function pointers provide a way to get around using
switches. Instead of transferring control to a case label we
invoke a function that is the value of variable containing a
function pointer. Instead of having a list of (value,label)
pairs we have a list of (value,function pointer) pairs.

We can express this in list in many ways, depending on what the
values are. Unlike the switch cases the values don't have to be
integers. If they are the integers in the range 0:n-1 the list
can be an array of function pointers and instead of having a
switch statement and a list of cases with their code we have
something like

func(args);

On the other hand if the values are strings we could have an
array of (key,function) pairs, search the array for the key,
and invoke the corresponding function.

The great advantage of this sort of thing is that we can replace
large switch statements within a single function with a series of
small functions, each handling a single case. The resulting code
(may be) is more compact and more readable.

However there are some gotchas. The two big ones are scope and
signature.

In a switch block the code within the cases is within the scope
of the containing function. What this means is that the code
within the case has access to the variables in the function
containing the switch. When you use the function pointer
technique, the called functions don't automatically have access
to the variables of the calling function. (You can fake it by
passing them through the calling sequence.) As a result the
called functions may have to do a bunch of makeup work.

The other gotcha is the signature problem. As a practical matter
all of the functions in the (value,function) list must have the
same prototype (signature) because they all are going to be
called the same way. What this means is that these functions
ususally have to be specially written within the context of being
equivalent to replacing a switch.

I hope this helps.
 
M

Michal Nazarewicz

Tomás Ó hÉilidhe said:
Here's a typical switch statement:

switch (user_input)
{
case 0: DeleteFile(); break;
case 1: CopyFile(); break;
case 2: RenameFile(); break;
case 4: MoveFile();
}

And here's the function pointer equivalent:

void (*const funcs[4])(void) = {DeleteFile,CopyFile,Renamefile,MoveFile};

funcs[user_input];

Forgot about "()" and to be precise you should check if user_input is >=
0 and <= 4.

Whether that is faster is another matter. Compiler may implement
a switch using an array of locations in program and achieve the same or
even higher speed (no need to call a function, simple jump).
 
T

Tomás Ó hÉilidhe

Michal Nazarewicz said:
Forgot about "()" and to be precise you should check if user_input is

The only redudant checks I use in my programs are asserts. If the input
to a function should be between 0 through 4, then I'll document it and
say that the behaviour is undefined if the input's out of range. Of
course though, I'll still make liberal use of assert.

/* MyFunc

input : Number between 0 through 4
Behaviour is undefined if out of range
*/

void Func(unsigned const i)
{
assert(i < 5);

int arr[5];

arr = 6;
}
 
A

Ark Khasin

Tom�������������������������������� said:
The only redudant checks I use in my programs are asserts.
May I suggest that's a very dubious practice.
Debug build crashes predictably; Release build (with NDEBUG #define'd)
either crashes unpredictably or wanders away mysteriously.
Use asserts for debugging and never for user input. On the API level,
your user can be another programmer.
- Ark
 
C

cr88192

May I suggest that's a very dubious practice.
Debug build crashes predictably; Release build (with NDEBUG #define'd)
either crashes unpredictably or wanders away mysteriously.
Use asserts for debugging and never for user input. On the API level, your
user can be another programmer.
- Ark

what I do depends a lot on context.

usually, I try to detect most cases that are dubious, but still workable,
and try to handle the situation gracefully (very often, this is in finding
an alternate way to do something, or returning some sort of error status).

in some cases, if the problem is particularly serious, very often I will
print some debugging messages and initiate a controlled crash.

even though windows doesn't much like the crashes (when they are unexpected
and I am not running a debugger, causing both crashes and annoying popup
messages), they are good in that they make it a lot easier to crash the
program just where I want it to be (sort of like a semi-permanent
breakpoint), which makes it a lot easier to try to recreate the problem in a
debugger, and then have it crash somewhere where I can easily examine the
state and can try to figure out what has gone wrong.

there are different ways to invoke crashes, but the one I typically use is
this:
*(int *)-1=-1;

on linux, this option works better though, since linux crashes give a
general debugger-usable core dump (sadly absent on windows...).


well, it works at least.
 
I

Ian Collins

cr88192 said:
there are different ways to invoke crashes, but the one I typically use is
this:
*(int *)-1=-1;
Why not assert(0), so you get the file and line without having to use a
debugger?
on linux, this option works better though, since linux crashes give a
general debugger-usable core dump (sadly absent on windows...).
I pity those poor windows developers :)
 
C

cr88192

Ian Collins said:
Why not assert(0), so you get the file and line without having to use a
debugger?

file and line, by themselves, are not often all that helpful (they are
better than nothing, but when something crashes, usually a lot more is
helpful).

often the exact state at the time of crash (local variables, globals, stack
parameters, ...) are very helpful in identifying a bug.

also, usually the kinds of problems that need this, are a lot more involved
than the simple "bad input" type problems assert is good at pointing out
(more often, it is along the lines of "something has gone fundamentally
wrong here").

so, neither assert nor abort will give all this. it is "too clean", the
debugger just thinks the program exited with an error code, which does not
allow much examination of the program state at the point of the crash.

all this is why I prefer to forcibly crash the program at a specific place,
and then use the debugger to examine the results.


assert is maybe good, if one does not have a debugger, and wants a very
simplistic error handling mechanism, but I wouldn't really use it over my
method, personally.

I pity those poor windows developers :)

sadly, I develop primarily on windows, but I guess, technically, I could
still be regarded as a cross-platform developer (I try to write code that
builds on both windows and linux, and test things on linux every once in a
great while...).

 
T

Tomás Ó hÉilidhe

May I suggest that's a very dubious practice.
Debug build crashes predictably; Release build (with NDEBUG #define'd)
either crashes unpredictably or wanders away mysteriously.


If there's a need for a check, then there should be one. There's no need
for several checks. Maybe the calling function needs to validate user
input before passing it to the function:

unsigned i;
scanf("%u",&i);

if (i > 4)
{
puts("Bad input, bailing out.");
exit(EXIT_FAILURE);
}

Func(i);

And then there are the times when no checks are necessary:

unsigned i = some_number % 5;

Func(i);
 
C

CBFalconer

Ian said:
cr88192 wrote:
.... snip ...


I pity those poor windows developers :)

Why? I get all that ability. Of course, I am using gcc under
DJGPP on winders.
 
I

Ian Collins

cr88192 said:
file and line, by themselves, are not often all that helpful (they are
better than nothing, but when something crashes, usually a lot more is
helpful).

often the exact state at the time of crash (local variables, globals, stack
parameters, ...) are very helpful in identifying a bug.

also, usually the kinds of problems that need this, are a lot more involved
than the simple "bad input" type problems assert is good at pointing out
(more often, it is along the lines of "something has gone fundamentally
wrong here").
One should never use assert for bad external inputs (inputs to the
application that is, not a function).
so, neither assert nor abort will give all this. it is "too clean", the
debugger just thinks the program exited with an error code, which does not
allow much examination of the program state at the point of the crash.
A decent debugger will stop on abort. Failing that, just set a breakpoint.
sadly, I develop primarily on windows, but I guess, technically, I could
still be regarded as a cross-platform developer (I try to write code that
builds on both windows and linux, and test things on linux every once in a
great while...).
Try going the other way...
and don't forget not to quote signatures!
 
R

Richard Heathfield

Ark Khasin said:
May I suggest that's a very dubious practice.

No, it's an excellent practice, albeit rather hard to achieve. Assertions
should indeed be redundant, and therefore should never, ever fire. If,
therefore, they do, it means that something is wrong with the program.
That's their job - to show that the program is broken in a particular way.

If the programmer manages to make all other necessary checks without any
redundancy, his program will fly. In practice, however, it's rather
difficult to do.
 
M

Michal Nazarewicz

Tomás Ó hÉilidhe said:
The only redudant checks I use in my programs are asserts.

Still pieces of code using switch and array of function-pointers you
have mentioned are not equivalent -- they will be if this (possibly
redundant in some situations but we don't know that with such limited
piece of code) check. :]
 
M

Michal Nazarewicz

cr88192 said:
there are different ways to invoke crashes, but the one I typically use is
this:
*(int *)-1=-1;

How about:

#define CRASH do { \
fprintf(stderr, "%s: %d: going to crash\n", __FILE__, __LINE__); \
*(int*)-1 = 42; \
} while (0)
 
C

cr88192

Michal Nazarewicz said:
How about:

#define CRASH do { \
fprintf(stderr, "%s: %d: going to crash\n", __FILE__, __LINE__); \
*(int*)-1 = 42; \
} while (0)

this also works (nowhere was I saying one can't use a macro), just,
personally I tended not use a macro for this (and also tended also to write
out the printf manually...).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,431
Messages
2,571,677
Members
48,796
Latest member
Greg L.

Latest Threads

Top