Why are statements with no effect considered legal in C?

Statements with no effect are permitted because it would be more difficult to ban them than to permit them. This was more relevant when C was first designed and compilers were smaller and simpler.

An expression statement consists of an expression followed by a semicolon. Its behavior is to evaluate the expression and discard the result (if any). Normally the purpose is that the evaluation of the expression has side effects, but it's not always easy or even possible to determine whether a given expression has side effects.

For example, a function call is an expression, so a function call followed by a semicolon is a statement. Does this statement have any side effects?

some_function();

It's impossible to tell without seeing the implementation of some_function.

How about this?

obj;

Probably not -- but if obj is defined as volatile, then it does.

Permitting any expression to be made into an expression-statement by adding a semicolon makes the language definition simpler. Requiring the expression to have side effects would add complexity to the language definition and to the compiler. C is built on a consistent set of rules (function calls are expressions, assignments are expressions, an expression followed by a semicolon is a statement) and lets programmers do what they want without preventing them from doing things that may or may not make sense.


The statements you listed with no effect are examples of an expression statement, whose syntax is given in section 6.8.3p1 of the C standard as follows:

 expression-statement:
   expressionopt ;

All of section 6.5 is dedicated to the definition of an expression, but loosely speaking an expression consists of constants and identifiers linked with operators. Notably, an expression may or may not contain an assignment operator and it may or may not contain a function call.

So any expression followed by a semicolon qualifies as an expression statement. In fact, each of these lines from your code is an example of an expression statement:

i = i + 2;
5;
i;
printf("i: %d\n", i);

Some operators contain side effects such as the set of assignment operators and the pre/post increment/decrement operators, and the function call operator () may have a side effect depending on what the function in question does. There is no requirement however that one of the operators must have a side effect.

Here's another example:

atoi("1");

This is calling a function and discarding the result, just like the call printf in your example but the unlike printf the function call itself does not have a side effect.


Simple Statements in C are terminated by semicolon.

Simple Statements in C are expressions. An expression is a combination of variables, constants and operators. Every expression results in some value of a certain type that can be assigned to a variable.

Having said that some "smart compilers" might discard 5; and i; statements.


One benefit to allowing such statements is from code that's created by macros or other programs, rather than being written by humans.

As an example, imagine a function int do_stuff(void) that is supposed to return 0 on success or -1 on failure. It could be that support for "stuff" is optional, and so you could have a header file that does

#if STUFF_SUPPORTED
#define do_stuff() really_do_stuff()
#else
#define do_stuff() (-1)
#endif

Now imagine some code that wants to do stuff if possible, but may or may not really care whether it succeeds or fails:

void func1(void) {
    if (do_stuff() == -1) {
        printf("stuff did not work\n");
    }
}

void func2(void) {
    do_stuff(); // don't care if it works or not
    more_stuff();
}

When STUFF_SUPPORTED is 0, the preprocessor will expand the call in func2 to a statement that just reads

    (-1);

and so the compiler pass will see just the sort of "superfluous" statement that seems to bother you. Yet what else can one do? If you #define do_stuff() // nothing, then the code in func1 will break. (And you'll still have an empty statement in func2 that just reads ;, which is perhaps even more superfluous.) On the other hand, if you have to actually define a do_stuff() function that returns -1, you may incur the cost of a function call for no good reason.

Tags:

C