【问题标题】:How to generate random variable names in C++ using macros?如何使用宏在 C++ 中生成随机变量名称?
【发布时间】:2010-11-08 02:33:07
【问题描述】:

我正在用 C++ 创建一个宏,它声明一个变量并为其分配一些值。根据宏的使用方式,宏的第二次出现可以覆盖第一个变量的值。例如:

#define MY_MACRO int my_variable_[random-number-here] = getCurrentTime();

使用它的另一个动机是避免为变量选择特定名称,以便它与开发人员使用宏最终选择的名称相同。

有没有办法在 C++ 的宏中生成随机变量名?

-- 编辑--

我的意思是唯一的,但也是随机的,一旦我可以在一个块中使用我的宏两次,在这种情况下,它会生成如下内容:

int unique_variable_name;
...
int unique_variable_name;

在这种情况下,为了唯一,两个变量名都必须是随机生成的。

【问题讨论】:

标签: c++ variables random macros naming


【解决方案1】:

尝试以下方法:

// One level of macro indirection is required in order to resolve __COUNTER__,
// and get varname1 instead of varname__COUNTER__.
#define CONCAT(a, b) CONCAT_INNER(a, b)
#define CONCAT_INNER(a, b) a ## b

#define UNIQUE_NAME(base) CONCAT(base, __COUNTER__)

void main() {
  int UNIQUE_NAME(foo) = 123;  // int foo0 = 123;
  std::cout << foo0;           // prints "123"
}

__COUNTER__ 可能存在可移植性问题。如果这是一个问题,您可以改用__LINE__,只要您每行不多次调用宏或在编译单元之间共享名称,就可以了。

【讨论】:

  • 这看起来很疯狂,但它确实有效。我恰好遇到了这个问题:__LINE__ 扩展到自身,而不是数字。我省略了PP_,现在我可以执行以下操作:#define FOR(ii, ll, uu) int CAT(FORlim, __LINE__) = (uu); for(int ii = (ll); ii &lt; CAT(FORlim, __LINE__); ++ii) - 这是任何while(0)-hacks 都无法实现的。这个答案应该更高。
  • 以及如何引用刚刚形成的唯一名称的变量?说,我有这个代码:int UNIQUE_NAME(nTest) = 100;。如何稍后在代码中查询该 nTest0 变量? PP_CAT(base, __COUNTER - 1) 不起作用。谢谢。
  • 为什么我们需要在宏中进行间接寻址,请阅读stackoverflow.com/a/13301627/264047
  • @Alexander Malakhov 感谢您提供的链接,我尝试了各种间接方式,但均未成功。如果您可以制作一个 UNIQUE_PREV_NAME(base) 的变体,那就太好了。
  • @Alexander Malakhov 我玩过各种 __COUNTER - 1 我可以想象。对 Godbolt(使用 -E gcc 键)的实验得出的结论是它根本不可行:没有办法让预处理器缩小数学表达式(例如,它不会将“10 - 1”转换为“9” )。无论如何感谢您的帮助。
【解决方案2】:

将 M4 添加到您的构建流程中?这种宏语言具有一些有状态的能力,并且可以成功地与 CPP 宏混合。这可能不是在 C 环境中生成唯一名称的标准方法,尽管我已经能够以这种方式成功地使用它。

根据您提出问题的方式,您可能不希望随机,顺便说一句。你想要独一无二的

你可以在宏扩展中使用__FILE____LINE__ 来获得你想要的唯一性......这些元变量是在源文件上下文中定义的,所以要小心确保你得到了什么您正在寻找(例如,同一行中有多个宏的危险)。

【讨论】:

  • 还有一个COUNTER宏,每次调用都会生成一个新的整数,但它是非标准的。
  • 哇,现在有评论格式了!无论如何,这应该是 COUNTER,前后有两个下划线。
  • 这对我不起作用,因为我可能在同一个文件中多次使用该宏,并稍后在另一个宏中引用它。 “__ COUNTER __”(我知道它们都在一起)可能会起作用,但我需要知道计数器的当前值而不增加它。
  • 你不能#define A_VAR UNIQUE_VAR_MACRO() \n int A_VAR = 1; printf("%x",A_VAR);.... #UNDEF A_VAR ??
  • @freitass - 看看下面 Dave Dopson 的回答。我想在我的宏中使用__LINE__,当使用## 进行标记粘贴时,它只是粘贴了__LINE__。但是,这可以被破解。但是,我的示例可以通过#define FOR(ii, ll, uu) for(int ii##lim = (uu), ii = (ll); ii &lt; ii##lim; ++ii) 解决,而无需此hack。 - 但这是 C++ 或 C99(用于范围界定)。旧 C 的类似 FOR-macro 需要 Dave 的技术。
【解决方案3】:

使用__COUNTER__(适用于 gcc4.8、clang 3.5 和 Intel icc v13、MSVC 2015)

#define CONCAT_(x,y) x##y
#define CONCAT(x,y) CONCAT_(x,y)
#define uniquename static bool CONCAT(sb_, __COUNTER__) = false

【讨论】:

  • 很好,我不知道##。
  • 这不起作用,__COUNTER__ 没有展开。预处理器的输出为:static bool sb___COUNTER__ = false;
  • @JeffB 你用的是什么编译器?我尝试了一些编译器(gcc、clang、icc)并且它工作正常。 godbolt.org/z/iAgc6t
  • 似乎有人在我添加该评论后编辑了答案。原来的直接使用 __COUNTER__,但是你需要间接使用 CONCAT 宏才能正常工作。
【解决方案4】:

在预处理器中生成唯一名称很困难。您可以得到的最接近的方法是将__FILE____LINE__ 拆分为popcnt 建议的符号。如果您确实需要生成唯一的全局符号名称,那么我会按照他的建议在您的构建系统中使用 M4 或 Perl 脚本之类的东西。

您可能不需要唯一的名称。如果您的宏可以强加一个新的范围,那么您可以使用相同的名称,因为它只会隐藏其他定义。我通常遵循在do { ... } while (0) 循环中包装宏的常见建议。这仅适用于作为语句的宏 - 而不是表达式。宏可以使用输出参数更新变量。例如:

#define CALC_TIME_SINCE(t0, OUT) do { \
     std::time_t _tNow = std::time(NULL); \
     (OUT) = _tNow - (t0); \
} while (0)

如果您关注few rules,您通常会很安全:

  1. 对宏中定义的符号使用前导下划线或类似的命名约定。这将防止出现与使用相同符号的参数相关的问题。
  2. 只使用一次输入参数并始终用括号括起来。这是使宏使用表达式作为输入的唯一方法。
  3. 使用do { ... } while (0) 成语确保宏仅用作语句并避免其他文本替换问题。

【讨论】:

  • 使用前导下划线不是一个好主意,因为这样生成的名称可能与实现保留的名称发生冲突,并且在任何情况下都是保留的。
  • 确实如此。宏的用户可能想要使用像 _tNow 这样的名称。我建议使用完整的宏名称作为宏使用的名称的前缀,在本例中为 CALC_TIME_SINCE_tNow
【解决方案5】:

你可以让宏用户给你一个名字,而不是让预处理器创建一个名字。

#define MY_MACRO(varname) int varname = getCurrentTime();

【讨论】:

    【解决方案6】:

    在我没有任何分析工具的情况下,我需要类似的东西,但我想计算特定代码块中有多少线程以及在该代码块中花费的时间(滴答声)每个线程的代码,在这种情况下,每个块都需要一个所有线程都可以访问的唯一静态变量,并且我需要稍后将该变量引用到 incr (我在实际代码中使用了日志记录 API 而不是 printf,但这也适用) .起初我认为我做了以下事情很聪明:

    #define PROF_START { \
        static volatile int entry_count##___FUNCTION__##__LINE__ = 0; int *ptc = &entry_count##___FUNCTION__##__LINE__; \
        clock_t start, end; \
        start = times(0); \
        (*ptc)++;
    

    但后来我意识到这很愚蠢,只要每个“静态”声明都是它自己的块,C 编译器就会为你做这件事:

    #include <stdio.h>
    #include <sys/times.h>
    
    #define PROF_START { \
        static int entry_count = 0; \
        clock_t start, end; \
        start = times(0); \
        entry_count++;
    
    
    #define PROF_END \
        end = times(0); \
        printf("[%s:%d] TIMER: %ld:%d\n" , __FUNCTION__, __LINE__, end-start, entry_count); \
        entry_count--; \
        }
    

    注意每个宏中的开/关括号。这不是严格的线程安全的,但出于我的分析目的,我可以假设 incr 和 decr 操作是原子的。这是一个使用宏的递归示例

    #define ITEM_COUNT 5
    
    struct node {
       int data;
       struct node *next;
     };
    
    revsort(struct node **head)
    {
      struct node *current = *head;
      struct node *next_item;
    
      while (current->next)
      {
    PROF_START
        next_item = current->next;
        current->next = next_item->next;
        next_item->next = *head;
        *head = next_item;
    PROF_END
      }
    }
    
    rrevsort(struct node **head)
    {
      struct node *current = *head;
      struct node *next_item = current->next;
    
    PROF_START
      current->next = 0;
      if (next_item)
      {
       *head = next_item;
        rrevsort(head);
        next_item->next = current;
      }
    PROF_END
    
    }
    
    printnode(struct node *head)
    {
      if (head)
      {
        printf("%d ", head->data);
        printnode(head->next);
      }
      else
        printf("\n");
    
    }
    
    main()
    {
    
      struct node node_list[ITEM_COUNT];
      struct node *head = &node_list[0];
      int i;
    
      for (i=0; i < ITEM_COUNT - 1; i++)
      {
    PROF_START
          node_list[i].data = i;
          node_list[i].next = &node_list[i+1];
    PROF_END
      }
      node_list[i].data = i;
      node_list[i].next = 0;
    
      printf("before\n");
      printnode(head);
      revsort(&head);
      printf("after\n");
      printnode(head);
      rrevsort(&head);
      printf("before\n");
      printnode(head);
    }
    

    额外提示,以上程序为常见面试题。摘自“nm -A”:

    macro:0804a034 b entry_count.1715
    macro:0804a030 b entry_count.1739
    macro:0804a028 b entry_count.1768
    macro:0804a02c b entry_count.1775
    

    【讨论】:

      【解决方案7】:

      这是生成上述单例模式的简洁宏定义。

      #define SINGLETON_IMPLIMENTATION(CLASS_NAME) static CLASS_NAME *g##CLASS_NAME = nil; + (CLASS_NAME *)instance { @synchronized(self) { if (g##CLASS_NAME == nil) g##CLASS_NAME = [self new]; } return g##CLASS_NAME; }
      
      #define SINGLETON_DECLARATION(CLASS_NAME) + (CLASS_NAME *)instance;
      

      【讨论】:

        【解决方案8】:

        虽然我认为这根本不可能,但你应该认真考虑用它来开设一门课程。

        如果你想让随机数组中的随机元素保持某个值,你可以这样做:

        std::vector< std::vector<int> > m_vec;
        

        然后将其包装在一个类中,这样开发者就只能设置一个数字了:

        void set(int foo)
        {
            m_vec[random()][random()] = foo;
        }
        

        你有什么理由想要它一个宏吗?随机变量名听起来很危险,如果它选择了代码中其他地方已经定义的东西怎么办?

        【讨论】:

        • 其实我并不“想要”它一个宏,但要解决的问题是一个宏。你的回答给了我一个想法,我创建了一个类来保存值(管理一个列表而不是每次都声明一个变量)。
        猜你喜欢
        • 2011-08-13
        • 1970-01-01
        • 2013-01-19
        • 1970-01-01
        • 1970-01-01
        • 2018-03-24
        • 1970-01-01
        • 2019-08-23
        • 1970-01-01
        相关资源
        最近更新 更多