0

I have this macro in my C code:

#define ASSERT(ret, num) \  // make sure ret === num
        if (ret != num) { \
            fprintf(stderr, "Error [%d] at line [%d] in function [%s]. Date: [%s] Time: [%s]\n", \
                    ret, __LINE__, __FUNCTION__, __DATE__, __TIME__); \
            exit(ret); \
        }

Then i call it like so (all arguments are integers):

ASSERT(errNo, MPI_SUCCESS);
ASSERT(aliveNeighbors, 8);
ASSERT(newGrid, !NULL);

I get errors like (GCC v5.4):

expected identifier or ‘(’ before ‘if’
   if (ret != num) { \
error: stray ‘\’ in program
   ASSERT(errNo, MPI_SUCCESS);
error: stray ‘\’ in program
  ASSERT(aliveNeighbors, 8);
stray ‘\’ in program
  ASSERT(newGrid, !NULL);

What is wrong here?

17
  • 4
    Delete comment after \ (// make sure ret === num) Commented Jul 27, 2017 at 17:13
  • 2
    Note that __DATE__ and __TIME__ are the date and time when the code was compiled, not when the program was run. It is unlikely to be useful information. Commented Jul 27, 2017 at 17:20
  • 3
    Note that you can't use // … <eol> comments inside macros. You can use /* … */ comments inside, as long as the backslash continuation is after the comment. Those can also extend over multiple lines, of course (and don't need backslashes at the ends of the lines), but that's probably not a particularly good idea either. You could use // … <eol> at the end of the last line of a macro. Commented Jul 27, 2017 at 17:23
  • 2
    @lurker: You can't use // … <eol> inside a macro at all. If you have \ at the end of the line, it is the comment that is extended, not the macro body. Commented Jul 27, 2017 at 17:24
  • 3
    @RestlessC0bra: "It works" in the sense that ASSERT(ret, num) becomes a no-op macro. The line splicing from backslash-newline happens before comments are analyzed, and the // … \<eol> continues the comment onto the next line; any \ at the end of the next line also continues the comment. Commented Jul 27, 2017 at 17:27

1 Answer 1

3

Given the macro definition:

#define ASSERT(ret, num) \  // make sure ret === num
        if (ret != num) { \
            fprintf(stderr, "Error [%d] at line [%d] in function [%s]. Date: [%s] Time: [%s]\n", \
                    ret, __LINE__, __FUNCTION__, __DATE__, __TIME__); \
            exit(ret); \
        }

you have a number of problems, many of them visible in your error messages.

The backslash-newline line splicing occurs in phase 2 of the processing defined in the standard (ISO/IEC 9899:2011) at §5.1.1.2 Translation phases:

  1. Physical source file multibyte characters are mapped, in an implementation-defined manner, to the source character set (introducing new-line characters for end-of-line indicators) if necessary. Trigraph sequences are replaced by corresponding single-character internal representations.

  2. Each instance of a backslash character (\) immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. Only the last backslash on any physical source line shall be eligible for being part of such a splice. A source file that is not empty shall end in a new-line character, which shall not be immediately preceded by a backslash character before any such splicing takes place.

  3. The source file is decomposed into preprocessing tokens7) and sequences of white-space characters (including comments). A source file shall not end in a partial preprocessing token or in a partial comment. Each comment is replaced by one space character. New-line characters are retained. Whether each nonempty sequence of white-space characters other than new-line is retained or replaced by one space character is implementation-defined.

  4. Preprocessing directives are executed, macro invocations are expanded, and _Pragma unary operator expressions are executed. If a character sequence that matches the syntax of a universal character name is produced by token concatenation (6.10.3.3), the behavior is undefined. A #include preprocessing directive causes the named header or source file to be processed from phase 1 through phase 4, recursively. All preprocessing directives are then deleted.

(Phases 5-8 complete the processing but are not germane to this discussion.)

Note that comments are removed in phase 3, and the preprocessing proper occurs in phase 4, on tokenized input.

The first line of your macro definition is:

    #define ASSERT(ret, num) \  // make sure ret === num

This defines the body of the macro as \. You cannot place comments after the backslash that is to continue a macro, and you cannot use // … <eol> style comments in the body of a macro. If you added a backslash after the comment, it would continue the comment onto the next line, and the macro body would still be empty. You can use /* … */ comments with care:

    #define ASSERT(ret, num)   /* make sure ret === num */ \

This would work correctly. Because the original macro has the trailing // … <eol> comment, that completes the macro. The first error message is:

expected identifier or ‘(’ before ‘if’
   if (ret != num) { \

This is because the if line in the 'macro definition' is actually not a part of the macro, and the if is not expected in the context where it appears.

The other three errors are essentially the same:

error: stray ‘\’ in program
   ASSERT(errNo, MPI_SUCCESS);

The macro expanded to just a backslash, but you can't have stray backslashes in the text of a valid C program, hence the error message. When backslashes appear, they're always in stylized contexts with specific other characters following (digits, certain letters, certain punctuation symbols, newline).

You also have issues with the body of the macro:

  • __DATE__ and __TIME__ are the date and time when the code was compiled (preprocessed), not the date and time when the code was run. They're seldom useful, and not useful in this context. If you want date and time information for when the program is run, you'll define and call a function which determines and formats the current date/time.
  • __FUNCTION__ is not standard; __func__ is the standard predefined identifier.
  • As written, the macro cannot be safely used in some contexts, such as:

    if (alpha == omega)
        ASSERT(retcode, 0);
    else if (gamma == delta)
        gastronomic_delight(retcode, gamma, alpha);
    

    The else clause is incorrectly associated with the if in the (semi-corrected) ASSERT and not with the alpha == omega test.

Assembling these changes into a workable solution:

#define ASSERT(ret, num) \
            do { \
                if ((ret) != (num)) err_report(ret, __LINE__, __func__); \
            } while (0)

where you have:

extern _Noreturn void err_report(int err, int line, const char *func);

This function formats the message and reports it on standard error, determining time and date if appropriate. It also exits (hence the _Noreturn attribute — a feature of C11. You might prefer to #include <stdnoreturn.h> and use noreturn instead of _Noreturn. You might or might not decide to add __FILE__ as a value passed to the function; it would need another parameter.

Note, too, that the invocation:

ASSERT(newGrid, !NULL);

is suspect. It translates to if ((newGrid) == (!0)) which is not what you intended. You have to work a bit harder to test for not-null pointers:

ASSERT(newGrid != NULL, 1);

This compares the value in newGrid with NULL, generating 1 if the value is non-null, and 0 if it is null, and that is compared with the 1 argument. Note that the error number for this test would not be helpful — it would be 0.

If I've missed any crucial points in the comments, please let me know.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks a lot. If you could implement err_report as well that would be great.
You can look at the code in stderr.c and stderr.h on GitHub. There's an err_report() function in there, but it is a different design than the one I devised here. However, the code in stderr.[ch] covers all the bases you're likely to need and good few that you won't need. Note that your messages are very verbose. That doesn't matter if they're infrequent, but could become exasperating to look at if they become frequent. However, that's probably more an issue for another day. (The code in those files is not trivial to read.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.