Into the void
Introduction The static keyword is often cited as the most over-used keyword in C and C++. In C, there are three uses. I only recently realized that the void keyword has four uses. I mean, I knew the four circumstances in which you’d use void , but it never “clicked” in my head that there were four uses - and that’s greater than static ’s three. I think it’s because all of void ’s uses are somewhat related to “nothingness” whereas static ’s uses are largely unrelated, hence more noticeable. History Originally, C didn’t have the void keyword. To write a function that didn’t return a value, you could simply omit the return type: print_locus( file, line, col, msg ) char file, msg; { printf( "%s:%d,%d: %s", file, line, col, msg ); } This function is written in the original “K&R” dialect of C - before function prototypes were adopted from C++ - where parameters were specified twice: the first time for the order, and the second time for the types. Any parameter not explicitly given a type was presumed to be int ; any function not explicitly given a return type was presumed to return int . You could even omit the return statement. In those days, C compilers were fairly lax about enforcing things (hence the creation of lint). But the compiler still generated a machine instruction to return a (garbage) value. Since the caller wasn’t expecting a return value anyway, it worked out. If you wanted to write marginally cleaner code, you’d just return 0 . This state of affairs, specifically that there was no way to state explicitly that a function returned no value, annoyed Stephen Bourne (yes, the creator of the Bourne Shell). After complaining to Ritchie, and Ritchie realizing that not returning anything from a function could save one machine instruction, void (Bourne’s suggested name) was added to C. Since then, void slowly had use-cases added to it. It’s also influenced and been adopted by other languages including C++ (obviously), C#, Java, D, and Swift (though Swift spells it Void with a capital “V”). Function Prototypes Perhaps the biggest influence C++ had on C was the backporting of function prototypes. In K&R C, if you declared (not defined) a function at all, it was only because it returned a type other than int : double sqrt(); // K&R C: no parameter information! Function declarations declared only its return type; its parameters were not declared at all. You just had to ensure that you called the function with the right types. C++ added function prototypes because Bjarne wanted the ability to overload functions based on their arguments. But prototypes are also very useful for ensuring that the number and types of arguments actually match a function’s parameters. Hence, they were back-ported to C. But in C++, a function declared like f() means f takes no parameters whereas in K&R C, all it means was that f was a function (with no information about its parameters at all). To add prototypes to C yet still allow existing C code for backwards compatibility, void was re-used, e.g., f(void) means f takes no parameters in C - not that f takes one parameter of type void since you can’t declare either a variable or parameter of type void . To increase compatibility between C and C++ header files, f(void) means the same thing in C++ though the use of void is otherwise unnecessary. Pointers Although void objects can’t exist, you curiously can have pointers to void , e.g., void p . Such pointers are use to point to anything (the opposite of nothing), but the actual type is unknown. As such, you can never dereference a void pointer. Before void in K&R C, char , in addition to pointing at either a single character or more commonly an array of characters, i.e., a string, was also used to point to anything, i.e., a chunk of bytes. However, using void is much clearer (even though you often have to cast a void to a char* anyway so you can increment it). Casting The last use is casting to void . There are actually two use-cases. One is making it clear that you’re intentionally discarding the return value of a function (that doesn’t return void ): (void)printf( "hello, world!\n" ); Such a use means you’re calling the function only for its side effects. Did you even realize that printf returns a value? It returns the number of characters printed. This can actually be useful when you’re trying to line-up columns in multiple lines of output. Such a use is rare and is mostly for the benefit of the human reader, not the compiler. One exception is if the function is declared [[nodiscard]] , but you want to discard its return value anyway. The other use-case is casting a parameter to void as is done here in cdecl : static bool c_ast_visitor_type( c_ast_t const *ast, user_data_t user_data ) { (void)user_data; // ... } This means that you’re intentionally not using the value of a parameter, i.e., casting its value to “nothing.” This also suppresses a warning that the parameter isn’t used. (In C23, you can simply omit the parameter’s name.) Conclusion So now you know that void , not static , is the most over-used in C. The reason for over-use of keywords in general is that adding any new keyword to a language breaks existing programs because somebody, somewhere, used it to name something. In void ’s case, the different uses are at least mostly related to “nothingness.” Top comments (0)
Comments
No comments yet. Start the discussion.