Pages

Saturday, February 28, 2015

Programming in C: Few Tidbits #5

1. Splitting a long string into multiple lines

Let's start with an example. Here is a sample string. It'd be nice to improve readability by splitting it into multiple lines.

const char *quote = "The shepherd drives the wolf from the sheep's for which the sheep thanks the shepherd as his liberator, while the wolf denounces him for the same act as the destroyer of liberty. Plainly, the sheep and the wolf are not agreed upon a definition of liberty.";

Couple of ideas.

  • Line continuation: split the string anywhere at white space, and end the line with a backslash (\). Repeat until done.

    Backslash (\) is the continuation character often referred as backslash-newline.

    eg.,

    const char *quote = "The shepherd drives the wolf from the sheep's for which \
                            the sheep thanks the shepherd as his liberator, while \
                            the wolf denounces him for the same act as the destroyer \
                            of liberty. Plainly, the sheep and the wolf are not agreed \
                            upon a definition of liberty.";
    

    The C preprocessor removes the backslash and joins the following line with the current one. This is repeated until all lines are joined. However in the above example, indentation becomes part of the actual string thus a bunch of unwanted whitespaces appear in the final string. Besides, it is not possible to include comments at the end of any of those lines [after the line continuation character] if you ever wanted to. Both of these minor issues can be avoided with string literal concatenation (discussed next).

    Compiling the above with Solaris Studio C compiler results in the following output.

    The shepherd drives the wolf from the sheep's for which                         the sheep thanks the shepherd as his liberator, while                   the wolf denounces him for the same act as the destroyer             of liberty. Plainly, the sheep and the wolf are not agreed                      upon a definition of liberty.
    
  • String literal concatenation: split the string anywhere at white space, and end the line with a pair of quotes ("). Start the next line with quotes, and repeat until done.

    eg.,

    const char *quote = "The shepherd drives the wolf from the sheep's for which "         /* dummy comment */
                           "the sheep thanks the shepherd as his liberator, while "        // another dummy comment
                           "the wolf denounces him for the same act as the destroyer "
                           "of liberty. Plainly, the sheep and the wolf are not agreed "
                           "upon a definition of liberty.";
    

    Adjacent string literals are concatenated at compile time. Comments outside the string literals are ignored, and the concatenated string will not include indented whitespaces unless they are within the string literals (delimited by quotes).

    Printing the above results in the following output.

    The shepherd drives the wolf from the sheep's for which the sheep thanks the shepherd as his liberator, while the wolf denounces him for the same act as the destroyer of liberty. Plainly, the sheep and the wolf are not agreed upon a definition of liberty.

2. Simultaneous writing to multiple streams

The straight forward approach is to make multiple standard I/O library function calls to write to desired streams.

eg.,

..
fprintf(stdout, "some formatted string");
fprintf(stderr, "some formatted string");
fprintf(filepointer, "some formatted string");
..

This approach may work well as long as there are only a few occurrences of such writing. However if there is a need to repeat it many times over the lifetime of a process, it can be simplified by wrapping all those function calls that write to different streams into a function so a single call to the wrapper function takes care of writing to different streams. If the number of arguments in the formatted string is not constant or not known in advance, one option is to make the wrapper function a variadic function so that it accepts a variable number of arguments. Here is an example.

The following example writes all messages to the log file, writes only informative messages to standard output (stdout) and writes only fatal errors to the high priority log. Without the logfmtstring() wrapper function, the same code would have had five different standard I/O library calls rather than just three that the sample code has.

% cat multstreams.c
#include <stdio.h>
#include <string.h>
#include <stdarg.h>

FILE *log, *highprioritylog;

void logfmtstring(const char *fmtstr, ...) {

        va_list args;
        va_start(args, fmtstr);

        vfprintf(log, fmtstr, args);

        if (strstr(fmtstr, "[info]") != NULL) {
                vfprintf(stdout, fmtstr, args);
        }

        if (strstr(fmtstr, "[fatal]") != NULL) {
                vfprintf(highprioritylog, fmtstr, args);
        }

        va_end(args);

}

void some_function(..) {

        log = fopen("app.log", "w");
        highprioritylog = fopen("app_highpriority.log", "w");

        ...

        logfmtstring("[info] successful entries: %d. failed entries: %d\n", completecount, failedcount);
        logfmtstring("[fatal] billing system not available\n");
        logfmtstring("[error] unable to ping internal system at %s\n", ip);

        ...

        fclose(log);
        fclose(highprioritylog);

}

% cc -o mstreams multstreams.c

% ./mstreams
[info] successful entries: 52. failed entries: 7

% cat app.log
[info] successful entries: 52. failed entries: 7
[fatal] billing system not available
[error] unable to ping internal system at 10.135.42.36

% cat app_highpriority.log
[fatal] billing system not available
%

Web search keywords: C Variadic Functions

3. Declaring variables within the scope of a CASE label in a SWITCH block

It is possible to declare and use variables within the scope of a case label with one exception -- the first statement after a case label should be a statement or an expression, but not a declaration. If not compiler throws an error during compilation such as the following.

% cat switch.c
#include <stdio.h>

int main() {
        switch(NULL) {
                default:
                        int cyear = 2015;
                        printf("\nin default ..");
                        printf("\nyear = %d", cyear);
        }
}

% cc -V -o switch switch.c
cc: Sun C 5.12 SunOS_sparc Patch 148917-08 2014/09/10
acomp: Sun C 5.12 SunOS_sparc Patch 148917-08 2014/09/10
"switch.c", line 6: syntax error before or at: int
"switch.c", line 8: undefined symbol: cyear
cc: acomp failed for switch.c

The above failure can be fixed by either moving the variable declaration to any place after a valid statement if possible, or by adding a dummy or null statement right after the case label.

eg.,

1. Move the declaration from right after the case label to any place after a valid statement.

/* works */
switch(NULL) {
        default:
                printf("\nin default ..");
                int cyear = 2015;
                printf("\nyear = %d", cyear);
}

2. Add a dummy or null statement right after the case label.

/* works */
switch(NULL) {
        default:
                ; // NULL statement
                int cyear = 2015;
                printf("\nin default ..");
                printf("\nyear = %d", cyear);
}

3. Yet another option is to define or create scope using curly braces ({}) for the case where variables are declared.

/* works too */
switch(NULL) {
        default:
        {
                int cyear = 2015;
                printf("\nin default ..");
                printf("\nyear = %d", cyear);
        }
}

Also see: Keyword – switch, case, default

No comments:

Post a Comment