tags:

views:

170

answers:

5

Hi,

I thought a C string can be initialized with one and only one quoted string. I just wonder how is this correct?

char const help_message [] =   
   "Usage: %s [options] files ...\n"   
   "\n"   
   "Options include:\n"   
   " --verbose -v    Be verbose\n"   
   " --help -h       Print this help message\n"   
   " --output -o     Specify output file\n"     
   "\n" ;   

 printf (help_message, argv [0]) ;
+11  A: 

The compiler will automatically concatenate adjacent strings.

This can be quite useful to increase readability, as in your example, or with some preprocessor functions:

#define LOG(x) printf("%s", "Logging: " x)

LOG("HeyHey");

Pretty contrived example, but gets the point across.

GMan
Not if the string might contain a `%` character. `puts("Logging: " x)` is a possibility, though.
caf
@Johannes: Like caf said, the first argument for `printf` is a string to be formatted, and I was going to avoid that (plus it's a natural reaction to protect against buffer-attacks). In reality, this function might actually benefit more from just placing `x` in with `"Logging: "`, to allow for formatting.
GMan
`printf("Logging: %s", x)` makes more sense and doesn't require x to be a string literal. And don't forget puts adds a newline which wasn't in the example (fputs doesn't).
Roger Pate
@caf: What would be the problem with string containing a `%` character in this case???
AndreyT
AndreyT: `printf("something with %s")` (notice no more parameters) is an error. It will result in either a compile-time problem (GCC's -Wformat) or undefined behavior.
Roger Pate
Yes, but GMan's code looks differently. He's doing `printf("%s", "something with %s")`. I don't see any problems in this case.
AndreyT
AndreyT: My comment (and GMan's first comment) were both responses to a now-deleted comment that suggested just `printf("Logging: " x)`. Beware the retconning!
caf
+2  A: 

Because adjacent quoted strings are considered to be part of the same string by the compiler. The grammar of C specifically allows this.

John Weldon
+2  A: 

Don't be confused with an array of strings:

{"xxx" ,
"yyyy",
"3534"}

What you posted is a single string.

dtroy
+2  A: 

Your initializer contains only one string literal. What looks like multiple ""-enclosed string literals will actually be merged into a single string literal at the 6-th stage of translation (after preprocessing).

AndreyT
+1  A: 

Adjacent string literals are concatenated, and this is useful two ways: macros which combine strings and visualizing multi-line string literals such as you have above. Compare how that code would look otherwise:

char const help_message[] = "Usage: %s [options] files ...\n\nOptions include:\n --verbose -v    Be verbose\n --help -h       Print this help message\n --output -o     Specify output file\n\n";

Imagine trying to maintain that instead. In addition, if you use a multi-line string literal, you have to either escape newlines or deal with whatever the source code uses, which may not be '\n', and you'll have to watch your indentation carefully. All of which makes the code in your example better.

Here's an example of the macro case:

#define STRINGIZE_(v) #v
#define STRINGIZE(v) STRINGIZE_(v)

#define LOCATION __FILE__ ":" STRINGIZE(__LINE__)

#define MY_ASSERT(expr) do { \
   if (!(expr)) \
     some_function(LOCATION ": assertion failed in " \
                   __PRETTY_FUNCTION__ ": " #expr); \
} while (0)

(There are alternatives to this, such as passing separate parameters, and using the GCC-specific __PRETTY_FUNCTION__ like this is deprecated too, but the rest is handy and this is a decent "real" example, IMHO.)

Other issues brought up in that code to be aware of:

  • the single # is the preprocessor stringize operator (## is the other special preprocessor operator, for token pasting)
  • without the second stringize macro, you'll get "filename.c:__LINE__"
  • using do-while doesn't break if-else and requires the macro to be used as a statement rather than an expression
    • preventing use as an expression isn't always helpful, but it is what you want for assert-like macros

Breaking if-else example:

if (cond) MY_ASSERT(blah);
else other();

Expands to:

if (cond) do { ... } while(0);
else other();

Instead of:

if (cond) if (...) ...;
else other();

Which is the incorrect and surprising:

if (cond) {
  if (...) {
    ...;
  }
  else {
    other();
  }
}
Roger Pate