78

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.

dbush
  • 205,898
  • 23
  • 218
  • 273
Epidem7c
  • 877
  • 1
  • 3
  • 7
  • 27
    Somewhat related to why we invented `0` for addition and `1` for multiplication. The *do nothing* operation (for anything) seems useless on any singular, practical use-case (low level of abstraction) but becomes essential for some generics (eg. In maths, algebra allows us to solve infinite categories of problems effortlessly - without being able to multiply by `1` we'd need to consider many cases in our calculations - exponentially more cases as the number of parameters increases). – Elliott May 23 '22 at 14:25
  • 35
    It's as useful as `int identity(int x) { return x; }`. Sometimes you just need it as default parameter where people can supply their own function for customize some algorithm. – Goswin von Brederlow May 23 '22 at 16:14
  • 7
    Absolutely. It's a convenient way to turn off a feature that is implemented as a pointer to a function. If enabled, pointer points to the implementation. If disabled, pointer points to `Nothing`. – Jeff Holt May 24 '22 at 02:00
  • Lazy error catching? – user1934286 May 24 '22 at 02:16
  • It's also similar to `/bin/true` and `/dev/null`. – chrylis -cautiouslyoptimistic- May 24 '22 at 03:49
  • Such a function can be moderately useful for debugging, as a spot to be able to put a breakpoint. – TLW May 24 '22 at 05:24
  • 4
    In C++, overriding a base class function that does do something, or equally in the base class that some child classes may need to overwrite. – mgh42 May 24 '22 at 05:40
  • unit testing, common practice is to replace dependencies with stub functions (mocking) – CaptianObvious May 24 '22 at 13:50
  • 1
    I always included something like this in every project so when my boss asked "What are you working on?" I could always say "Oh, nothing." – A. I. Breveleri May 24 '22 at 16:19
  • See also the [null object pattern](https://en.wikipedia.org/wiki/Null_object_pattern), which is useful so that consumers can accept an object instead of an optional object which they have to check the existence of; and [NOP instructions](https://en.wikipedia.org/wiki/NOP_(code)) which exist in many assembly/bytecode languages and have various purposes. – kaya3 May 24 '22 at 16:36
  • Of course there's no use for a function that does nothing, and how sure are you that " void Nothing() {} " falls into that category? The Question itself, and most of the exposition, really do seem to have no use but so what? Who says " void Nothing() {} " does nothing? I suggest whoever wrote whichever language you're citing thought it it did something useful. How could those two "nothings" be comparable? Note: "as much time as the compiler / interpreter gives it" changes what, here? – Robbie Goodwin May 24 '22 at 22:30
  • Rust isn't C, but Rust has a standard library function defined as `pub fn drop(_x: T) { }` and it's so useful it landed in the language prelude. The docs for [`std::convert::indenty`](https://doc.rust-lang.org/std/convert/fn.identity.html) includes a couple of relatively language agnostic ways to use do-nothing functions as well. – Aiden4 May 24 '22 at 22:32
  • 2
    This also reminds me of the questions about why computers have a NO-OP instruction. – Barmar May 25 '22 at 14:36
  • The R programming language has a library called `nothing`, which, when invoked, *unloads* all other libraries. Its sole purpose is to communicate to the developer that no external libraries are to be used. https://github.com/romainfrancois/nothing/#nothing – stevec May 25 '22 at 18:37
  • @mgh42 Overriding a function called "nothing" to do something would be a terrible practice though. This question seems to be about a function specifically designed to do nothing, not one that happens to do nothing. – nasch May 25 '22 at 20:52
  • 1
    @RobbieGoodwin You need to provide some evidence for your claim that _"I suggest whoever wrote whichever language you're citing thought it it did something useful"_. Your language agnostic speculation doesn't make it remotely true, nor does it shed any light on the issue. – skomisa May 26 '22 at 01:14
  • 1
    Not a duplicate, and it relates to C++ rather than C, but [How wasteful would it be to call an empty function?](https://stackoverflow.com/q/20646579/2985643) may be of interest. – skomisa May 26 '22 at 01:27

13 Answers13

129

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);
dbush
  • 205,898
  • 23
  • 218
  • 273
  • 7
    Just to extend this wonderfully succinct answer: We *could* provide the same functionality by adding a boolean parameter; something like `requires_callback`, and use an if statement in the function ... but it'd be slower and simply more of a pain to use! – Elliott May 23 '22 at 14:50
  • 26
    @Elliott Or better yet, the function could accept a `NULL` pointer for the callback and check for that value before calling it. – dbush May 23 '22 at 14:51
  • 1
    Yes, that'd actually be a decent alternative. Come to think of it, this sort of `null operation` function really makes way more sense in languages like C++ where you can throw in a template argument that's a function - if it does nothing then the compiler will actually remove it as an input entirely. – Elliott May 23 '22 at 14:55
  • 36
    Any option where you pass in some sort of indicator not to use the callback requires that person who wrote the function taking the callback thought of the possibility that sometimes the user might not want to do anything, and then specifically wrote some code to support that possibility. Passing a do-nothing function works even if the API implementer didn't anticipate this usage (or where you're just not certain whether they did or not, which is the big weakness of the null pointer idea; the API signature will not communicate whether null is handled properly). So it's a little less coupled. – Ben May 24 '22 at 03:21
  • 12
    This reminds me a bit of the "This page intentionally left blank" text you sometimes see on exam papers to make it clear that the student hasn't accidentally been given a blank piece of paper – Rowan May 24 '22 at 07:03
  • 1
    See https://en.wikipedia.org/wiki/Null_object_pattern for the general case – Pete Kirkham May 24 '22 at 12:30
  • @Elliott I half remember hearing about a system where `call 0` was a no-op -- or maybe the OS would put a `ret` at address 0. One gotcha, though, is that the callback might unintentionally be a null pointer, and this prevents your program from terminating with a null pointer error (which might be good or bad depending on the circumstances). – Kyle Miller May 24 '22 at 15:46
  • 5
    May I add another valid use-case? My company's software uses plugins heavily. Each plugin has an API. Let's say that you could choose between plugin foo/bar and foo/baz. Plugin bar might do something with a particular function while plugin baz might do nothing with that function, but both plugins need to implement it as part of the plugin API. Internally it makes use of callback functions. – mgarey May 24 '22 at 16:08
  • 3
    @Rowan I worked on a project where the tech lead decided we needed _exactly that_, and changed all of our optional callbacks to mandatory (this in Typescript where the signature was clear that nulls were handled), using `doNothing` (or `bounce` which just returned its argument) everywhere explicitly, after one too many times code he’d reviewed and approved turned out to have forgotten a necessary callback rather than intentionally elected not to use one. (We were working with an international team so it was painful to have these kinds of back-and-forth over silly mistakes.) – KRyan May 25 '22 at 13:08
  • @dbush using `NULL` is never better and some languages don't even have it. – user11153 May 25 '22 at 13:58
  • 2
    @user11153 This question is about C, so it doesn't matter what other languages have. And in the context of C, `NULL` is perfectly fine as a sentinel value for a pointer. – dbush May 25 '22 at 14:01
  • If you like this code, then maybe not: `if (x != 0) sum += x;` – Wyck May 26 '22 at 17:27
  • @KyleMiller Yeah, though code should only rely on getting program-terminating null-pointer errors if it's aware that this is an implementation detail on modern hardware with MMUs configured by the OS to do that, but back in the old days some computers did things like having address zero (or memory page zero) always contain zeroes (this is a big part of why C says it's undefined behavior rather than "your program will terminate/terminate in an undefined/implementation-defined manner"), and in low-level embedded/privileged code it's possible for the null address to just be a normal address. – mtraceur Jun 23 '23 at 00:02
47

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.

abelenky
  • 63,815
  • 23
  • 109
  • 159
  • 5
    Not only is the code cleaner when there is no NULL checks, there is a small performance benefit as well. For the vast majority of cases this is insignificant but say if you are writing an interrupt handler state machine, the if test penalty might be significant. And it gives deterministic and uniform execution behaviour are good characteristics in general, and might be vital for a well function interrupt handler. So when you can chose A or B where there is insignificant difference in the general case but in some (rare) cases B is much better than A, then B should be your goto approach. – hlovdal May 25 '22 at 16:50
37

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.

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • 4
    an example from the standard library would be a `signal` handler. – Yakov Galka May 23 '22 at 14:11
  • 4
    @YakovGalka: SIG_IGN please, and that's the POSIX library not the standard library. – Joshua May 23 '22 at 14:12
  • 7
    @Joshua: The standard library does have [`signal()`](https://en.cppreference.com/w/c/program). Also, [constraint handlers](https://en.cppreference.com/w/c/error/set_constraint_handler_s) if you are using Annex K. – DevSolar May 23 '22 at 14:16
  • 2
    @DevSolar: That turns out to be one of the places where it says it has something and really doesn't. Ever try using it on DOS? It doesn't work. Taking a floating point exception doesn't raise SIGFPE. I tried it. There's a SIGTERM there. It's utterly useless on both DOS and Win32. – Joshua May 23 '22 at 14:20
  • 1
    @Joshua: It's a matter of the implementation. You *can* make it work (as a library implementor), but things being as they are in the signal handling department, most (including me) don't really bother, since there is very little gain to it in the first place. -- Anyway, what I wanted to point out here is that Yakov's comment wasn't wrong. – DevSolar May 23 '22 at 14:35
  • @Joshua There are also more than just `signal()` on the list of standard function that requires a function pointer. [`atexit()`](https://en.cppreference.com/w/c/program/atexit) for example. – Ted Lyngmo May 23 '22 at 14:36
  • 1
    @TedLyngmo: I don't think anybody's going to dispute there's no point passing an empty function to atexit, qsort, or bsearch. I only want examples where passing empty functions has some use. – Joshua May 23 '22 at 14:37
  • 1
    @Joshua: Just FYI, if you *are* interested. Signals are inherently OS-specific, as it is the OS that *raises* them (calling the registered functions) in the first place; the library just provides a way of *registering* those functions. Non-POSIX OS's provide non-POSIX logics here. From the standard standpoint, this means you *cannot* provide a OS-independent *definition* of exactly how `signal()` will behave; there cannot be cross-platform portability. So the rather underdefined specs the standard has is the best it can do without breaking compatibility with non-POSIX platforms. – DevSolar May 24 '22 at 09:10
  • @DevSolar: I can annotate whichever way; but observing the difference between SIG_IGN and an empty function still isn't going to appear without talking about `exec()`. – Joshua May 24 '22 at 14:11
  • @Joshua An empty signal handler function does not have the same semantics as `SIG_IGN`, even within a single program execution. In particular, you need the former if you want to be able to exit early from syscalls with `EINTR`. – Joseph Sible-Reinstate Monica May 25 '22 at 04:15
31

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.

Daniel R. Collins
  • 893
  • 1
  • 8
  • 22
  • 7
    ..and in fact all sorts of frameworks have scaffolding, skeletons, templates with functions labelled "put code here" that are not necessarily used, but exist because a predetermined monolithic sequence of processing occurs. They stay stubs in production if that code is never required, particular for pre and post processing before and after callbacks, and especially if the coding for the payload chunk is automated or in a closed library.. – mckenzm May 24 '22 at 17:59
  • @mckenzm Good point re _"They stay stubs in production if that code is never required..."_. You might consider reworking your comment into an answer since I don't see that use case being mentioned anywhere else. – skomisa May 26 '22 at 01:35
  • Combining this in Python with the `pass` statement is a very often used situation for me when I don't have implementation details down yet. – iced May 26 '22 at 03:08
13

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.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
13

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
Davislor
  • 14,674
  • 2
  • 34
  • 49
12

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
}
Trevortni
  • 688
  • 8
  • 23
  • 3
    Your debugger should allow you to put a breakpoint at arbitrary locations. Under the hood, it would do something like emit an `int 3` instruction (in the x86 world). This is a workaround for an inadequate tool, not a reason for a first-class language feature. And even if you had an inadequate tool, there are virtually limitless no-ops you could insert that would be valid C, even if C did not allow empty functions. – Cody Gray - on strike May 24 '22 at 23:27
  • 7
    I agree that the debugger should allow you to put a breakpoint at arbitrary locations. And yet here we are. Anyway, who said anything about first-class language features? The OP asked for reasons no-op functions might be useful, and this certainly applies. – Trevortni May 24 '22 at 23:41
  • Very nice, I actually just used this to debug something – infinitezero May 25 '22 at 12:57
  • 1
    This is particularly useful in environments that only support a few hardware breakpoints and you don't want the performance overhead of software breakpoints. One breakpoint inside the function will trigger no matter where the function is called from. Very handy when software breakpoints alter timing enough that bugs don't reproduce. – bta May 25 '22 at 19:01
  • @CodyGray I wonder whether these "arbitrary locations" include nested function calls, like the `daz()` call in `foo(bar(), baz(daz()));` (but not on `daz` function that may be called from other points in the program). Of course, one could switch to disassembly to set a breakpoint there, but are there any C level debuggers capable of this? – Ruslan May 25 '22 at 19:12
  • @CodyGray The lines are pretty fuzzy between inadequate tools, incompetent users, and just wanting to get a problem solved (without shaving too many yaks) and moving on. So, personally, I find myself writing quick do-nothing functions to set breakpoints on pretty regularly, and I'm not apologetic about this. (Usually I set the breakpoint in the do-nothing function itself, not on the line where it's called. Typically there's something abut the code where it's called that makes setting breakpoints there difficult or impossible — thus, the do-nothing function.) – Steve Summit Jun 16 '22 at 16:42
6

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.

Simon Richter
  • 28,572
  • 1
  • 42
  • 64
  • 3
    I think that might vary between languages. It's true for externally-linked languages, but not for all compiled languages. – Toby Speight May 24 '22 at 08:42
  • 1
    You bring up a good point. However, those functions are not doing nothing. I'm not being a smart-butt here; I'm interested in your answer. So, as a board member of the Language Lawyer Board Association, would `void Nothing() {}` provide anything useful (i.e. optimization barriers or other)? – shrewmouse May 24 '22 at 12:18
  • @shrewmouse, that can be optimized out. – Simon Richter May 24 '22 at 13:29
  • 1
    @shrewmouse The answer you may get from another board member would be typical lawyer-wiggling: "The compiler refused to give us that guarantee. It reserves the right to optimize in any way it sees fit. But the compiler is contractually bound to ensure that you won't notice anything except your program running faster." – Peter - Reinstate Monica May 24 '22 at 15:59
  • 10
    @Peter-ReinstateMonica: Decades ago, it was observed that "Every program has at least one bug and can be shortened by at least one instruction — from which, by induction, one can deduce that every program can be reduced to one instruction which doesn't work." The goal of modern compilers is to find that program. – supercat May 25 '22 at 05:40
  • @shrewmouse: No, that could inline and optimize away, unless you define it (for GCC only) as `__attribute__((noinline,noipa)) void Nothing(){}` to forbid the compiler from inlining it, or from doing inter-procedural optimization that lets other functions look inside the non-inline implementation and see that it doesn't touch variables they're using. At which point you might as well use `asm("" ::: "memory")` which is the safely-portable (to all GNU C implementations including clang) version of what this answer tries to do with GNU C Basic Asm `asm(" ")`. – Peter Cordes May 26 '22 at 19:24
  • Some GCC version a few years ago changed non-empty Basic Asm statements to imply a memory clobber, but that's not documented (thus not officially supported), and was only done as a sop to broken code like this. https://gcc.gnu.org/wiki/ConvertBasicAsmToExtended - don't use Basic Asm functions, except for `naked` functions. On GCC older than that, `asm(" ")` is the same as `asm("")`, the same as `asm("" ::: )` *without* a `"memory"` clobber. – Peter Cordes May 26 '22 at 19:40
  • @shrewmouse: See also [How does a mutex lock and unlock functions prevents CPU reordering?](https://stackoverflow.com/a/50951199) re: compile-time memory ordering and escape analysis to determine which locals have to be "in sync". – Peter Cordes May 26 '22 at 19:42
5

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.

kackle123
  • 261
  • 2
  • 8
  • 1
    As part of some debugging for a timing issue, I wanted to see how many cycles an empty while loop was taking in C#, and likewise a while loop calling a no-op function. I don't remember the details, but it was quite revealing, especially when I later, more out of interest, compared it to the equivalent C++ on an ARM processor. – ouflak May 25 '22 at 06:40
  • Oh yes, padding with nops. Along with filler jmps or guaranteed branch. Whatever gives you the extra T-Cycle for the least PC steps. Often seen in the tail end of delay loops. – mckenzm May 26 '22 at 04:23
5

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.

bta
  • 43,959
  • 6
  • 69
  • 99
1

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.

AMDG
  • 1,118
  • 1
  • 13
  • 29
  • Personally, though, I find there's a big tradeoff between purity and pragmatism. To assert that "each of these is a better design that makes your code and intent well-expressed" or that the more-explicit design "will be more maintainable and portable as well" ignores the sometimes significant costs (to readability and even maintainability) of the bloat and verbosity imposed by the more-explicit, more-"pure" scheme. – Steve Summit Jun 16 '22 at 16:53
  • @SteveSummit I'm not sure of what bloat and verbosity you are referring to. The above code in my answer towards the end refers to more or less idealistic interfaces in general. In practice, you probably would have a table of bits and send a single byte, rather than two bytes in the form of bools. Bloat and verbosity are defects. Unfortunately, C is bound to have that necessarily because it's a language that's been passed down through two organizations now, and none of them could get it right the first time with KnR, hence what I believe is the bloat and verbosity you refer to. – AMDG Jun 16 '22 at 17:09
  • It is more often than not that bugs and errors arise because the programmer has either not been thorough in the consideration of his algorithm's implementation, or actively decided against supporting something that is a known possibility perhaps at the time believing it good for performance or "getting work done". Obvious example: an if-else chain with missing true or false blocks; a switch that fails to handle certain cases; a function not doing a range check and getting a value out of scope instead; etc., the cost of which is irrelevant concerning, e.g., branching on a NULL pointer. – AMDG Jun 16 '22 at 17:11
1

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.

Jon Reeves
  • 2,426
  • 3
  • 14
1

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.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108