Home > Software design >  Is it valid/recommended to use string literals as the result of an expression using the ternary oper
Is it valid/recommended to use string literals as the result of an expression using the ternary oper

Time:09-17

Is the following code valid/good practice in C?

int x = 1;
printf(x == 1 ? "%d second elapsed" : "%d seconds elapsed", x);

It compiles fine, so I assume it is fine (also since it is just syntactical sugar for an if-else block), but if anyone has some additional insight, I would appreciate it, thank you.

P.S., I assume the same goes for C ?

CodePudding user response:

Yes!

Opinionated: After wading though tons of code written by programmers writing kernels, drivers and misc. utility programs that are usually considered "good", I think the consensus is that "it's fine". Not only is it safe - it can also be made easily readable.

A small, non-opinionated, note: Compilers are allowed to make string literals overlap. If you have two string literals, "Hello world" and "world" and compile your program will full optimization, you may find that the pointer to 'w' in "world" is actually within the "Hello world" string literal, since they are both valid, null-terminated strings.

However, that optimization cannot be applied to strings like yours, since the different part is in the middle of the string literal.

What that means is that the compiler must store two complete strings for your ternary operator, making it no better than using an if statement.

I therefore suggest:

printf("%d second%s elapsed", x, x == 1 ? "" : "s");

Another opinion of mine is that doing it like this also makes it easier to read.

CodePudding user response:

TL,DR: pass a string literal as the format string unless you really have no choice.

Is it valid C? Yes, sure. printf wants its first argument to be a string (a null-terminated array of characters), and it's getting it. Whatever the value of x is, this string contains exactly one printf format, which expects an int argument. And printf receives an int argument. So all is fine, as far as the C language is concerned.

Is it good practice? No. There are several advantages to passing a string literal as the format string to functions like printf, scanf, etc. If you pass a string literal, that gives the compiler (and other static analyzers) the opportunity to complain if you aren't passing correctly typed arguments. If you pass something more complex, the compiler may not be able to tell. Furthermore, passing a string literal gives the compiler more optimization opportunities: many compilers will break down printf calls into lower-level functions when the format string is constant: printf("%d seconds elapsed", x) will be compiled into something like __builtin_printf_int(x) fputs("", stdout) (plus error checking). If the format string is variable, it has to be parsed at runtime.

In this simple example, compilers are likely to recognize that there are only two possible format strings and to perform the check and optimization. In fact, let's make the program wrong:

#include <stdio.h>
void foo(long x) {
    printf(x == 1 ? "%d second elapsed" : "%d seconds elapsed", x);
}

Here's what GCC has to say about it:

a.c: In function ‘foo’:
a.c:3:23: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long int’ [-Wformat=]
     printf(x == 1 ? "%d second elapsed" : "%d seconds elapsed", x);
                      ~^
                      %ld
a.c:3:45: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long int’ [-Wformat=]
     printf(x == 1 ? "%d second elapsed" : "%d seconds elapsed", x);
                                            ~^
                                            %ld

You're getting two warnings, one for each of the two formats. But in more complex cases, the compiler would give up and just pass the dynamically constructed string to printf at runtime, with no opportunity to bail out if the parameters are wrong.

As long as your program hard-codes English for messages, there's a simple solution: make the pluralization a separate argument.

printf("%d second%s elapsed", x, x == 1 ? "" : "s");

This doesn't work well with internationalization, since different languages have different rules (for example some languages need the singular for 0, some have a different form for 2 and 3-or-more, some need a plural on “elapsed”, etc.). If you need internationalization, you aren't passing a constant string to printf anyway.

  • Related