【问题标题】:'goto *foo' where foo is not a pointer. What is this?'goto *foo' 其中 foo 不是指针。这是什么?
【发布时间】:2016-05-16 04:17:41
【问题描述】:

我在玩 labels as values 并最终得到了这段代码。

int foo = 0;
goto *foo;

我的 C/C++ 经验告诉我 *foo 意味着 dereference foo 并且这不会编译,因为 foo 不是指针。但它确实编译。这实际上是做什么的?

gcc (Ubuntu 4.9.2-0ubuntu1~12.04) 4.9.2,如果重要的话。

【问题讨论】:

  • 这是一个已知的错误。请参阅我的更新答案。

标签: c++ c gcc language-lawyer goto


【解决方案1】:

这是 gcc 中的一个已知错误。

gcc 有一个documented extension 允许以下形式的声明

goto *ptr;

其中ptr 可以是void* 类型的任何表达式。作为此扩展的一部分,将一元 && 应用于标签名称会产生标签的地址,类型为 void*

在你的例子中:

int foo = 0;
goto *foo;

foo 显然是 int 类型,而不是 void* 类型。 int 值可以转换为 void*,但只能通过显式强制转换(空指针常量的特殊情况除外,此处不适用)。

表达式*foo 本身被正确诊断为错误。还有这个:

goto *42;

编译没有错误(如果我正确读取汇编代码,生成的机器代码似乎是跳转到地址42)。

快速实验表明 gcc 生成相同的汇编代码

goto *42;

正如它所做的那样

goto *(void*)42;

后者是对文档扩展的正确使用,如果出于某种原因,您想跳转到地址 42,则可能应该这样做。

我已经提交了一个bug report——它很快就被关闭了,因为它是 2007 年提交的 this bug report 的副本。

【讨论】:

  • 这和跳转到foo地址一样吗?
  • @Ælex:不会跳转到foo的地址;它跳转到foo中包含的地址。
  • 取消引用 void* 并不比取消引用 int 更“正确”...
  • @Leushenko:goto *expr,其中expr 的类型为void*,是对记录在案的gcc 扩展的有效使用。它在 ISO C 中无效,但标准明确允许扩展。
  • @Leushenko 忽略 goto * 扩展,取消引用 void* 在 C 中是完全有效的。它在严格符合的程序中是没有用的,因为你唯一能对结果做的就是丢弃结果,或者以C99开头,立即重新取它的地址,但没有规定说它是无效的。在 C++ 中是不同的。 C++ 通过将 * 限制为指向对象的指针和指向函数的类型,使取消引用 void * 无效。该问题同时标记为 C 和 C++。我认为不应该。这就是它造成的混乱。
【解决方案2】:

似乎是一个 GCC 错误。这是一个clang输出作为比较。看来这些是我们应该预料到的错误。

$ cc -v
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.3.0
Thread model: posix
$ cc goto.c 
goto.c:5:7: warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'const void *' [-Wint-conversion]
        goto *foo;
             ^~~~
goto.c:5:2: error: indirect goto in function with no address-of-label expressions
        goto *foo;
        ^
1 warning and 1 error generated.

goto.c源代码:

int main(int argc, char const *argv[])
{
    int foo = 0;
    goto *foo;
}

【讨论】:

    【解决方案3】:

    这不是错误,而是GCC's Labels and Values extension 的结果。我猜他们想到了快速跳跃表和 JIT 之类的东西。使用这个(错误)功能,您可以跳转到函数

    // c
    goto *(int *)exit;
    // c++
    goto *reinterpret_cast<int *>(std::exit);
    

    并做一些非常有品位的事情,例如跳入 字符串字面量

    goto *&"\xe8\r\0\0\0Hello, World!Yj\1[j\rZj\4X\xcd\x80,\f\xcd\x80";
    

    Try it online!

    别忘了指针算术是允许的!

    goto *(24*(a==1)+"\xe8\7\0\0\0Hello, Yj\1[j\7Zj\4X\xcd\x80\xe8\6\0\0\0World!Yj\1[j\6Zj\4X\xcd\x80,\5\xcd\x80");
    

    我会将其他结果作为练习留给读者(argv[0]__FILE____DATE__ 等)

    请注意,您需要确保您对跳转到的内存区域具有可执行权限。

    【讨论】:

    • 事实上这是一个错误。它被记录为允许goto *ptr;,其中ptr 的类型为void*goto *foo; 其中foo 的类型为int* 违反了gcc 自己的扩展文档。见my answerthis bug report
    • @KeithThompson 我看到文档在哪里指定标签的地址类型为void *,但它在哪里指定不允许goto * 的非void * 参数?
    • "例如,goto *ptr; 任何void * 类型的表达式都是允许的。"
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-03
    • 2011-10-21
    • 2011-11-23
    • 2020-10-03
    • 1970-01-01
    • 1970-01-01
    • 2011-10-05
    相关资源
    最近更新 更多