Is there any practical use for a function that does nothing?

Viewed 10141

Would there be any use for a function that does nothing when run, i.e:

void Nothing() {}

Note, I am not talking about a function that waits for a certain amount of time, like sleep(), just something that takes as much time as the compiler / interpreter gives it.

13 Answers

Such a function could be necessary as a callback function.

Supposed you had a function that looked like this:

void do_something(int param1, char *param2, void (*callback)(void))
{
    // do something with param1 and param2
    callback();
}

This function receives a pointer to a function which it subsequently calls. If you don't particularly need to use this callback for anything, you would pass a function that does nothing:

do_something(3, "test", Nothing);

When I've created tables that contain function pointers, I do use empty functions.

For example:

typedef int(*EventHandler_Proc_t)(int a, int b); // A function-pointer to be called to handle an event
struct 
{
   Event_t             event_id;
   EventHandler_Proc_t proc;
}  EventTable[] = {    // An array of Events, and Functions to be called when the event occurs
    {  EventInitialize, InitializeFunction },
    {  EventIncrement,  IncrementFunction  },
    {  EventNOP,        NothingFunction    },  // Empty function is used here.
};

In this example table, I could put NULL in place of the NothingFunction, and check if the .proc is NULL before calling it. But I think it keeps the code simpler to put a do-nothing function in the table.

Yes. Quite a lot of things want to be given a function to notify about a certain thing happening (callbacks). A function that does nothing is a good way to say "I don't care about this."

I am not aware of any examples in the standard library, but many libraries built on top have function pointers for events.

For an example, glib defines a callback "GLib.LogFunc(log_domain, log_level, message, *user_data)" for providing the logger. An empty function would be the callback you provide when logging is disabled.

One use case would be as a possibly temporary stub function midway through a program's development.

If I'm doing some amount of top-down development, it's common for me to design some function prototypes, write the main function, and at that point, want to run the compiler to see if I have any syntax errors so far. To make that compile happen I need to implement the functions in question, which I'll do by initially just creating empty "stubs" which do nothing. Once I pass that compile test, I can go on and flesh out the functions one at a time.

The Gaddis textbook Starting out with C++: From Control Structures Through Objects, which I teach out of, describes them this way (Sec. 6.16):

A stub is a dummy function that is called instead of the actual function it represents. It usually displays a test message acknowledging that it was called, and nothing more.

A function that takes arguments and does nothing with them can be used as a pair with a function that does something useful, such that the arguments are still evaluated even when the no-op function is used. This can be useful in logging scenarios, where the arguments must still be evaluated to verify the expressions are legal and to ensure any important side-effects occur, but the logging itself isn't necessary. The no-op function might be selected by the preprocessor when the compile-time logging level was set at a level that doesn't want output for that particular log statement.

As I recall, there were two empty functions in Lions' Commentary on UNIX 6th Edition, with Source Code, and the introduction to the re-issue early this century called Ritchie, Kernighan and Thompson out on it.

The function that gobbles its argument and returns nothing is actually ubiquitous in C, but not written out explicitly because it is implicitly called on nearly every line. The most common use of this empty function, in traditional C, was the invisible discard of the value of any statement. But, since C89, this can be explicitly spelled as (void). The lint tool used to complain whenever a function return value was ignored without explicitly passing it to this built-in function that returns nothing. The motivation behind this was to try to prevent programmers from silently ignoring error conditions, and you will still run into some old programs that use the coding style, (void)printf("hello, world!\n");.

Such a function might be used for:

  • Callbacks (which the other answers have mentioned)
  • An argument to higher-order functions
  • Benchmarking a framework, with no overhead for the no-op being performed
  • Having a unique value of the correct type to compare other function pointers to. (Particularly in a language like C, where all function pointers are convertible and comparable with each other, but conversion between function pointers and other kinds of pointers is not portable.)
  • The sole element of a singleton value type, in a functional language
  • If passed an argument that it strictly evaluates, this could be a way to discard a return value but execute side-effects and test for exceptions
  • A dummy placeholder
  • Proving certain theorems in the typed Lambda Calculus

Another temporary use for a do-nothing function could be to have a line exist to put a breakpoint on, for example when you need to check the run-time values being passed into a newly created function so that you can make better decisions about what the code you're going to put in there will need to access. Personally, I like to use self-assignments, i.e. i = i when I need this kind of breakpoint, but a no-op function would presumably work just as well.

void MyBrandNewSpiffyFunction(TypeImNotFamiliarWith whoKnowsWhatThisVariableHas)
{
  DoNothing(); // Yay! Now I can put in a breakpoint so I can see what data I'm receiving!
  int i = 0;
  i = i;    // Another way to do nothing so I can set a breakpoint
}

From a language lawyer perspective, an opaque function call inserts a barrier for optimizations.

For example:

int a = 0;

extern void e(void);

int b(void)
{
    ++a;
    ++a;
    return a;
}

int c(void)
{
    ++a;
    e();
    ++a;
    return a;
}

int d(void)
{
    ++a;
    asm(" ");
    ++a;
    return a;
}

The ++a expressions in the b function can be merged to a += 2, while in the c function, a needs to be updated before the function call and reloaded from memory after, as the compiler cannot prove that e does not access a, similar to the (non-standard) asm(" ") in the d function.

In the embedded firmware world, it could be used to add a tiny delay, required for some hardware reason. Of course, this could be called as many times in a row, too, making this delay expandable by the programmer.

Empty functions are not uncommon in platform-specific abstraction layers. There are often functions that are only needed on certain platforms. For example, a function void native_to_big_endian(struct data* d) would contain byte-swapping code on a little-endian CPU but could be completely empty on a big-endian CPU. This helps keep the business logic platform-agnostic and readable. I've also seen this sort of thing done for tasks like converting native file paths to Unix/Windows style, hardware initialization functions (when some platforms can run with defaults and others must be actively reconfigured), etc.

At the risk of being considered off-topic, I'm going to argue from a Thomistic perspective that a function that does nothing, and the concept of NULL in computing, really has no place anywhere in computing.

Software is constituted in substance by state, behavior, and control flow which belongs to behavior. To have the absence of state is impossible; and to have the absence of behavior is impossible.

Absence of state is impossible because a value is always present in memory, regardless of initialization state for the memory that is available. Absence of behavior is impossible because non-behavior cannot be executed (even "nop" instructions do something).

Instead, we might better state that there is negative and positive existence defined subjectively by the context with an objective definition being that negative existence of state or behavior means no explicit value or implementation respectively, while the positive refers to explicit value or implementation respectively.

This changes the perspective concerning the design of an API.

Instead of:

void foo(void (*bar)()) {
    if (bar) { bar(); }
}

we instead have:

void foo();

void foo_with_bar(void (*bar)()) {
    if (!bar) { fatal(__func__, "bar is NULL; callback required\n"); }
    bar();
}

or:

void foo(bool use_bar, void (*bar)());

or if you want even more information about the existence of bar:

void foo(bool use_bar, bool bar_exists, void (*bar)());

of which each of these is a better design that makes your code and intent well-expressed. The simple fact of the matter is that the existence of a thing or not concerns the operation of an algorithm, or the manner in which state is interpreted. Not only do you lose a whole value by reserving NULL with 0 (or any arbitrary value there), but you make your model of the algorithm less perfect and even error-prone in rare cases. What more is that on a system in which this reserved value is not reserved, the implementation might not work as expected.

If you need to detect for the existence of an input, let that be explicit in your API: have a parameter or two for that if it's that important. It will be more maintainable and portable as well since you're decoupling logic metadata from inputs.

In my opinion, therefore, a function that does nothing is not practical to use, but a design flaw if part of the API, and an implementation defect if part of the implementation. NULL obviously won't disappear that easily, and we just use it because that's what currently is used by necessity, but in the future, it doesn't have to be that way.

Besides all the reasons already given here, note that an "empty" function is never truly empty, so you can learn a lot about how function calls work on your architecture of choice by looking at the assembly output. Let's look at a few examples. Let's say I have the following C file, nothing.c:

void DoNothing(void) {}

Compile this on an x86_64 machine with clang -c -S nothing.c -o nothing.s and you'll get something that looks like this (stripped of metadata and other stuff irrelevant to this discussion):

nothing.s:

_Nothing:                               ## @Nothing
    pushq   %rbp
    movq    %rsp, %rbp
    popq    %rbp
    retq

Hmm, that doesn't really look like nothing. Note the pushing and popping of %rbp (the frame pointer) onto the stack. Now let's change the compiler flags and add -fomit-frame-pointer, or more explicitly: clang -c -S nothing.c -o nothing.s -fomit-frame-pointer

nothing.s:

_Nothing:                               ## @Nothing
    retq

That looks a lot more like "nothing", but you still have at least one x86_64 instruction being executed, namely retq.

Let's try one more. Clang supports the gcc gprof profiler option -pg so what if we try that: clang -c -S nothing.c -o nothing.s -pg

nothing.s:

_Nothing:                               ## @Nothing
    pushq   %rbp
    movq    %rsp, %rbp
    callq   mcount
    popq    %rbp
    retq

Here we've added a mysterious additional call to a function mcount() that the compiler has inserted for us. This one looks like the least amount of nothing-ness.

And so you get the idea. Compiler options and architecture can have a profound impact on the meaning of "nothing" in a function. Armed with this knowledge you can make much more informed decisions about both how you write code, and how you compile it. Moreover, a function like this called millions of times and measured can give you a very accurate measure of what you might call "function call overhead", or the bare minimum amount of time required to make a call given your architecture and compiler options. In practice given modern superscalar instruction scheduling, this measurement isn't going to mean a whole lot or be particularly useful, but on certain older or "simpler" architectures, it might.

These functions have a great place in test driven development.

class Doer {
public:
    int PerformComplexTask(int input) { return 0; } // just to make it compile
};

Everything compiles and the test cases says Fail until the function is properly implemented.

Related