C/C++中可破坏的命名范围

Fré*_*oni 5 c c++ macros idioms goto

介绍

这个问题来自于这个问题:命名循环成语:危险吗?.对于那些不想阅读原始问题的人来说,这是关于做这样的事情:

named(label1) for(int i = 0 ; i < 10 ; i++) {
    for(int j = 0 ; j < 10 ; j++) {
      if(some_condition)
            break(label1); // exit outer loop
      }
}
Run Code Online (Sandbox Code Playgroud)

这个新问题是关于"命名循环"习语的改进版本.如果你懒得阅读这篇文章,你可以直接转到这篇文章的"例子"部分,清楚地了解我在说什么.

设计缺陷

不幸的是,这个问题很快就结束了(后来又被重新开放)因为它更像是一个利弊辩论,而不是一个纯粹的技术问题.它似乎不符合SO Q&A格式.而且,我提出的代码有几个缺陷:

  • 该关键字break由宏重新定义
  • 宏使用小写字母书写
  • 它使一些可怕的东西可编译(至少使用MSVC):

    int foo() {
    
       named(label1) for(int i = 0 ; i < 10; i++)
       {
          if(some_condition)
          {
              break(label1);  // here it's ok, the behavior is obvious
          }   
       }
    
       break(label1); // it compiles fine without warning... but the behavior is pretty obscur!
    
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 它可能会破坏一些好看的代码.例如,由于范围问题,以下内容未编译.

    int foo() {
    
    named(label1)  for(int i = 0 ; i < 10 ; i++)
           named(label2)  for(int j = 0 ; j < 10 ; j++)
            if(i*j<15)
                cout << i*j << endl;
            else
                break(label2);
     }
    
    Run Code Online (Sandbox Code Playgroud)

更安全的实施

我试图解决所有这些问题,以获得命名循环安全版本.更一般地说,它也可以被称为可破坏范围,因为它可以用于过早退出任何范围,而不仅仅是循环.

下面是两个宏定义NAMEDBREAK:

    #define NAMED(bn)   if(const bool _YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_ = true)\
                            goto _named_loop_identifier__##bn##_;\
                        else\
                            _break_the_loop_labelled_##bn##_:\
                            if(true)\
                                {}\
                            else\
                                if(! _YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_)\
                                    goto _break_the_loop_labelled_##bn##_;\
                                else\
                                    _named_loop_identifier__##bn##_:\


    #define BREAK(bn) if(!_YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_){} \
                        else goto _break_the_loop_labelled_##bn##_
Run Code Online (Sandbox Code Playgroud)

它看起来很丑陋和繁琐,因为它还避免了某些可能由MSVC或GCC生成的警告,如"未使用的变量","未引用的标签"或"建议显式括号".此外,如果使用不正确,则不应编译,在这种情况下,错误消息将是可理解的.例如 :

    NAMED(loop1) for(int i = 0 ; i < 10; i++) {
        NAMED(loop2) for(int j = 0 ; j < i ; j++) {
            cout << i << "," << j << endl;
            if(j == 5) {
                BREAK(loop1);   // this one is okay, we exit the outer loop
            }
        }
        BREAK(loop2); // we're outside loop2, this is an error
    }
Run Code Online (Sandbox Code Playgroud)

以前的代码不会编译,第二个编译器错误消息BREAK:'_YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_loop2_`非常明确.

在提出我的问题之前,我提供了两个例子来说明这些结构的(相对)有用性:

break 外循环:

     NAMED(myloop) for(int i = 0 ; i < rows; i++) {
         for(int j = 0 ; j < cols ; j++) {
            if(some_condition) {
                 BREAK(myloop);
            }
         }
     }
Run Code Online (Sandbox Code Playgroud)

退出特定范围:

NAMED(myscope1) {
    cout<< "a";
    NAMED(myscope2)
    {
        cout << "b";
        NAMED(myscope3)
        {
            cout << "c";
            BREAK(myscope2);
            cout << "d";
        }
        cout << "e";
    }
    cout <<"f";
}
Run Code Online (Sandbox Code Playgroud)

这段代码打印:abcf.

我的问题

在定义我的问题之前,我必须定义它不是什么,因为我不希望在10分钟内看到我的主题被关闭.

不是 "这是一个好主意吗?",也不是"你怎么看?" 甚至"它有用吗?",因为stackoverflow似乎不是一个讨论的地方.无论如何,我已经知道了答案:"宏是邪恶的","Goto是邪恶的"和"使用lambdas".

相反,我的问题是关于编程错误的技术正确性和稳健性.我希望这个结构尽可能安全.

  • 用户是否有可能滥用它并仍然能够编译? 我试图解决原始实现的明显问题,但C++非常复杂,也许我错过了什么?

  • 它可以默默地打破一些好看的代码吗?这是我的主要关注点.它是否会干扰其他C++功能(异常处理,析构函数调用,其他条件语句或其他任何内容......)?

我的目标是证明这种结构是不内在危险.我已经知道在真实代码中使用它是一个非常糟糕的主意,因为其他程序员可能不清楚它,但它是否足够安全,足以用于个人项目?

编辑:布尔变量现在const(感谢Jens Gustedt).我将尝试稍后更换ifs for以检查它是否可以在使用时删除虚假警告:

if(true)
     BREAK(label);
Run Code Online (Sandbox Code Playgroud)

EDIT2:正如JensGustedt所注意到的,ifC中不允许声明中的变量声明(仅限C++).用循环替换它的另一个原因.

Jen*_*edt 1

我真的无法回答 C++ 方面的问题,但既然你也将其标记为 C ...

通常使用if/else构造来处理这样的事情并不是一个好主意,因为您总是会找到一个善意的 C 编译器,它会针对奇怪的else匹配和类似的事情找到适当的警告。

我通常使用for类似P99的结构。他们避免悬挂else(或有关它的虚假警告)。这for也是 C 中唯一可以按照需要放置局部变量的地方,它们在 C++ 中是不允许的if,也while不允许在 C++ 中使用。

在“安全”方面,您可能应该声明变量register bool const而不仅仅是bool. 因此没有人可以试图更改它,甚至可以在背后拿走它的地址来更改它。

但对于您使用它的特定目的,我不太for喜欢goto。您真正要做的是展开堆栈。在 C 中,有一种结构可以正确地做到这一点,即setjmp/ longjmp。在 C++ 中,它可能是try/ catch