Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C preprocessor concatenation outside of #define

I was wondering why we can't use token concatenation outside of defines.

This comes up when I want these at the same time:

  • conflict-free naming in a library (or for "generics")
  • debugability; when using a define for this then the whole code gets merged into a line and the debugger will only show the line where the define was used

Some people might want an example (actual question is below that):

lib.inc:

#ifndef NAME     #error includer should first define NAME #endif void NAME() { // works } // void NAME##Init() { // doesn't work // } 

main.c:

#define NAME conflictfree #include "lib.inc" int main(void) {     conflictfree();     // conflictfreeInit();     return 0; } 

Error:

In file included from main.c:2:0: lib.h:6:10: error: stray '##' in program  void NAME##Init();           ^ 

The rule of thumb is "concat only in define". And if I remember correctly: The reason is because of the preprocessor-phases. Question: Why does it not work. The phases-argument sounds like it was once an implementation-limitation (instead of a logical reason) and then found its way into the standard. What could be so difficult about accepting NAME##Init() if NAME() works fine?

like image 928
Bernd Elkemann Avatar asked Mar 10 '16 19:03

Bernd Elkemann


People also ask

What is ## in preprocessor?

The double-number-sign or token-pasting operator (##), which is sometimes called the merging or combining operator, is used in both object-like and function-like macros. It permits separate tokens to be joined into a single token, and therefore, can't be the first or last token in the macro definition.

What is Stringizing operator in C?

Stringizing operator (#) The number-sign or "stringizing" operator (#) converts macro parameters to string literals without expanding the parameter definition. It's used only with macros that take arguments.

What is concatenation of macro parameters?

Concatenation means joining two strings into one. In the context of macro expansion, concatenation refers to joining two lexical units into one longer one. Specifically, an actual argument to the macro can be concatenated with another actual argument or with fixed text to produce a longer name.

How #define works in C?

The #define creates a macro, which is the association of an identifier or parameterized identifier with a token string. After the macro is defined, the compiler can substitute the token string for each occurrence of the identifier in the source file.


Video Answer


1 Answers

Why was it is not an easy question. Maybe it's time to ask the standard committee why were they as crazy as to standardize (the now removed) gets() function as well?

Sometimes, the standard is simply brain-dead, whether we want it or not. The first C was not today's C. It was not "designed" to be today's C, but "grew up" into it. This has led to quite a few inconsistencies and design flaws on the road. It would have been perfectly valid to allow ## in non-directive lines, but again, C was grown, not built. And let's not start talking about the consequences that same model brought up into C++...

Anyway, we're not here to glorify the standards, so one way to get around this follows. First of all, in lib.inc...

#include <stdio.h>  #ifndef NAME     #error Includer should first define 'NAME'! #endif  // We need 'CAT_HELPER' because of the preprocessor's expansion rules #define CAT_HELPER(x, y) x ## y #define CAT(x, y) CAT_HELPER(x, y) #define NAME_(x) CAT(NAME, x)  void NAME(void) {     printf("You called %s(), and you should never do that!\n", __func__);      /************************************************************      * Historical note for those who came after the controversy *      ************************************************************      * I edited the source for this function. It's 100% safe now.      * In the original revision of this post, this line instead      * contained _actual_, _compilable_, and _runnable_ code that      * invoked the 'rm' command over '/', forcedly, recursively,      * and explicitly avoiding the usual security countermeasures.      * All of this under the effects of 'sudo'. It was a _bad_ idea,      * but hopefully I didn't actually harm anyone. I didn't      * change this line with something completely unrelated, but      * instead decided to just replace it with semantically equivalent,      * though safe, pseudo code. I never had malicious intentions.      */     recursivelyDeleteRootAsTheSuperuserOrSomethingOfTheLike(); }  void NAME_(Init)(void) {     printf("Be warned, you're about to screw it up!\n"); } 

Then, in main.c...

#define NAME NeverRunThis #include "lib.inc"  int main() {     NeverRunThisInit();     NeverRunThis();      return 0; } 
like image 156
3442 Avatar answered Sep 20 '22 18:09

3442