C unnecessary function prototypes

Consider these styles for a single source file program.

Form 1
Code:
void A(int x, int y);
int B(float x);

int main(int argc, const char *argv[])
{
    A(1, 2);
    return B(3.f);
}

void A(int x, int y)
{
    // some code goes here.
}

int B(float x)
{
    // do something with x
    return 0;
}

Form 2
Code:
void A(int x, int y)
{
    // some code goes here.
}

int B(float x)
{
    // do something with x
    return 0;
}

int main(int argc, const char *argv[])
{
    A(1, 2);
    return B(3.f);
}

Form 3
Code:
static void A(int x, int y)
{
    // some code goes here.
}
static int B(float x)
{
    // do something with x
    return 0;[/ICODE]
}

int main(int argc, const char *argv[])
{
   A(1, 2);
   return B(3.f);
}
I personally prefer form 3 above. Form 1 violates the DRY or don't repeat yourself principle. I see quite a bit of code using form 1 which doesn't actually need prototypes and also exports the symbols to the linker instead of marking them as static and file scope. If you add or remove parameters to functions A or B - or change the return type - you need to remember to touch both locations. Why incur the risk?

Unless I have a pair of codependent functions, I can usually avoid the prototype.

As before, no reply needed. I just like to complain. :rolleyes:
 
I prefer form 3 as well, but to each their own. There are people who can't look at their code without getting mad if functions are not sorted alphabetically (for binary search). Others who just love to have references in a legible list and see no reason they could not have that for static functions. Others who laugh at the idea that anyone is taking seriously the fact that functions order matter in C. Live and let live, they do whatever they want in _their_ codebase.

(I find very hard to defend form 2 instead of form 3, though… 😅 But I guess there will be people having reasons for that)
 
Header files, especially if the function is meant to be publicly visible.
Static means "reference does not exist outside this object file" (at least in C, not C++, but it can mean that in C++)
Although B doesn't really need to do anything with X because X is not modifiable in B (pass by value not by reference I think is the correct term)


Don't more current version of C standard let you get form 3 by doing:

int main(int argc, const char *argv[])
{
void A(int x, int y)
{
// some code goes here.
}

int B(float x)
{
// do something with x
return 0;
}
A(1, 2);
return B(3.f);
}
 
static for a function means it can't be used outside the current file, it's private API (and you can define static functions with the same name elsewhere in the codebase). That's also the point of putting the prototype at the top of file rather than in a header file: it allows to have prototypes, not having to bother with the order of functions, while still marking clearly that other files are not supposed to use this function (and enforcing they can't).
 
Static means "reference does not exist outside this object file" (at least in C, not C++, but it can mean that in C++)
The question was why the author wants to make all functions static. If it was preferrable, all functions would be static by default (plus another keyword for non-static).
 
There are people who can't look at their code without getting mad if functions are not sorted alphabetically (for binary search).
This is actually a requirement of style(9). It does make finding a function a little easier. Quoting style(9):

All functions are prototyped somewhere.

Function prototypes for private functions (i.e., functions not used
elsewhere) go at the top of the first source module. Functions local
to one source module should be declared static.

The downside is the duplication of text with the slight risk of not keeping them in sync. For projects outside the FreeBSD source tree, I prefer not to follow this. Note that style(9) does mandate static where appropriate.

Some other reasons for encouraging static are:
  • fewer symbols for the linker
  • less risk of symbol collisions
  • hiding implementation details prevents API abuse where some code elsewhere starts using the internal bits - making future re-design/refactor harder.
 
Don't more current version of C standard let you get form 3 by doing:
I've not seen nested functions in C. Apparently GCC allows this but it is not in the standard. No idea if clang allows this. Apparently this feature carries some elevated risks of stack and overflow attacks.
 
My style has always been to explicitly do function prototypes at the top (or in header file if they are exportable)

Then alphabetize the functions, separating them with
//----------------------------------------------------------------------------------
lines

and finally

// =======================================================
// =======================================================
// =======================================================
int main (int argc, char* argv[]) {
return 0;
}

This gives me good visual queus to know where functions break, and always
expect main() at the bottom

I'm also a huge advocate of requiring real and useful doxygen markups, but
equally annoying is a program that is more comments than it is real code.
 
I've not seen nested functions in C. Apparently GCC allows this but it is not in the standard. No idea if clang allows this. Apparently this feature carries some elevated risks of stack and overflow attacks.
Even in C++ I try to avoid nested functions. Object declarations...sure, but nested functions are often better implemented as lambdas in C++.
 
1. I use forward declarations (because: why not and inlining).
2. I create functions but do not place them in alphabetical order (because: ctags)
3. I use a lot of ("why"/"logic") comments (because: very few people use my programs and why not.)
4. I use doxygen headers but have started going away from them (because: I don't use doxygen much if at all and, LSP; vim + clang)
5. I use code folds (because: why not).
6. Nested inline functions (nope, not std). Seperated/external inline (sure).
 
There are header files for a reason ...
Clearly not, header is public API, private function is in C code with static statement.

A good implementation is the forth of #1 :oops:

static void func1(void);
static void func2(void);

int main(void){}

static void func1(void){}
static void func2(void{}

Strict prototyping style : when your declarations are ok the order of implementation doesn't matter, you can easily move code in source file and move function public <-> private.
 
Last edited:
This discussion opens up another related topic: function size and sourcefile size.

If you need to scroll to see a complete function then it is too long. Keep an idea as viewable on a single page. Functions can always be broken down and/or inlined. If you've got an if/else construct that is longer than three choices then review it as a candidate for indexed jumps (either thru case/switch or via indexed function pointers)

in my 40+ years of programming I don't see the need for huge sourcefiles. most should always be under 300 lines, and virtually NEVER move than 500. (including whitespace breaks)

Conserve vertical screen real-estate.
Do if (x) {fn()}
instead of
if (x)
{
fn()
}

and below is good because it doesn't waste curly brace lines
if (x) {
fn1()
} else {
fn2()
}
 
in my 40+ years of programming I don't see the need for huge sourcefiles. most should always be under 300 lines, and virtually NEVER move than 500. (including whitespace breaks)
Using multiple source files doesn't bloat the final binary. Compilation time is reduced with partial compilation of only modified source files. I'll never understand such big files like this one: FreeRTOS/FreeRTOS-Kernel/tasks.c 8871 lines of code, 427 occurrences for '#if', human cognitive load 1000% o_O
 
If you need to scroll to see a complete function then it is too long. Keep an idea as viewable on a single page. Functions can always be broken down and/or inlined.
Nice theory. Sometimes even works.

Sometimes doesn't work, and sometimes fails spectacularly. In my previous job, we had a 6000 line function. It was perfectly readable, mostly bug free (I remember funding one bug in it, not counting typos and superficial things), and the simplest way to structure the task. We seriously thought about breaking it up into two dozen functions, but then we decided that (a) the individual functions would no longer make sense, as they don't solve an identifiable and describable problem, but instead could only understood in the context of the larger function; (b) the big function carried a significant amount of internal state in it as local variables (all of which was actually needed, we audited that), and that meant that the smaller functions would all have very complex calls, with a dozen parameters; (c) the smaller functions having to change state of the larger function would have been hard to handle, either by using reference or pointer parameters, or packaging changes in complex (object) return types.

Admittedly, that is the extreme example, and I've never seen anything this complicated. But that function handled an extremely complicated work flow (which involved humans, hardware and software interacting).

Sure, we could have completely recoded it, for example into a state machine, with a pool of state at the center, and a variety of control flows (perhaps interlocked threads) handling the interactions. But orchestrating all these participants would have been hell, and bug prone.

If you've got an if/else construct that is longer than three choices then review it as a candidate for indexed jumps (either thru case/switch or via indexed function pointers)
Sometimes that works, and sometimes it is even idiomatic. Sometimes it is crazy, makes the code flow unreadable, and hides control in places where people will least expect it.

In your post, you are focused on code structures, such as long functions and if/else/while statement. I think what is actually more important is to look at data and state. What variables are in scope? Are there every any variables that are in scope (can be read or written) but their content is either undefined (for reading) or irrelevant (for writing)? Which pieces of code look at variables, and which pieces of code change them? And by this I don't so much mean local variables (the loop index "i" is not very important or dangerous), but variables that have longer lifetimes, that are kept in complex data structures or even globally.

Since the beginning of my career (nearly 50 years ago) I have been a proponent of Jackson Structured Programming, which had its bloom in the 70s in COBOL practice. If you know what data a program has in memory, and what that data means, and where it comes from and where it goes, you have won 90% of the war. The rest is then coding rather simple instruction flow.

Conserve vertical screen real-estate.
Do if (x) {fn()}
This one I completely agree with. Having as much code AS IS REASONABLE on the screen at once helps the brain see a bigger part of the picture, which helps it analyze the often complex job the code is doing. The issue here is "as is reasonable". I agree that wasting lines just on braces is bad. My favorite C style (which nobody else likes, but I can live with that):
C:
if (foo)
    do_one();
else {
    do_two_a();
    do_two_b(); }
No curly braces needed for single line blocks, and put the necessary curly braces on the same line as the code they are grouping. This only works if you have a good linter that checks indentation, and you treat every indentation warning as an error that needs to be fixed immediately, before even attempting to compile.

On the other hand: If a piece of code consists of three blocks, each of which is 5 or 6 lines long, and they have identifiable different purposes, then "wasting space" on a blank line is a good idea, because it helps the brain see the structure right away. Similarly, a complex 30 lines of code deserves a 5-line comment at the top explaining what it does and why and how, even if that makes it not fit on a single screen any longer.

With modern monitors, a lot of these concerns are just not relevant any longer. Restricting the width of code to 80 characters is silly, 100 fits on most good computers (even laptops, with two source code windows side by side).

One thing I wonder about is why we haven't started using proportional fonts for coding. Clearly we need fixed-width spaces to get the indentation at the beginning of the line right, but after that, proportionally spaced characters are easier to read and more compact.
 
Back
Top