24

I was playing around with labels as values and ended up with this code.

int foo = 0;
goto *foo;

My C/C++ experience tells me *foo means dereference foo and that this won't compile because foo isn't a pointer. But it does compile. What does this actually do?

gcc (Ubuntu 4.9.2-0ubuntu1~12.04) 4.9.2, if important.

Sarvadi
  • 550
  • 3
  • 13

3 Answers3

24

This is a known bug in gcc.

gcc has a documented extension that permits a statement of the form

goto *ptr;

where ptr can be any expression of type void*. As part of this extension, applying a unary && to a label name yields the address of the label, of type void*.

In your example:

int foo = 0;
goto *foo;

foo clearly is of type int, not of type void*. An int value can be converted to void*, but only with an explicit cast (except in the special case of a null pointer constant, which does not apply here).

The expression *foo by itself is correctly diagnosed as an error. And this:

goto *42;

compiles without error (the generated machine code appears to be a jump to address 42, if I'm reading the assembly code correctly).

A quick experiment indicates that gcc generates the same assembly code for

goto *42;

as it does for

goto *(void*)42;

The latter is a correct use of the documented extension, and it's what you should probably if, for some reason, you want to jump to address 42.

I've submitted a bug report -- which was quickly closed as a duplicate of this bug report, submitted in 2007.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • is this the same as jump to `foo` address? – Ælex Feb 06 '16 at 03:44
  • 1
    @Ælex: It doesn't jump to the address of `foo`; it jumps to the address contained in `foo`. – Keith Thompson Feb 06 '16 at 03:48
  • Dereferencing a `void*` isn't massively more "correct" than dereferencing an int... – Alex Celeste Feb 06 '16 at 04:19
  • 1
    @Leushenko: `goto *expr`, where `expr` is of type `void*`, is a valid use of a documented gcc extension. It's not valid in ISO C, but the standard explicitly permits extensions. – Keith Thompson Feb 06 '16 at 04:22
  • 2
    @Leushenko Ignoring the `goto *` extension, dereferencing a `void*` is perfectly valid in C. It's useless in strictly conforming programs, as the only thing you can do with the result is discard the result, or starting with C99, immediately take its address again, but there's no rule saying it's invalid. It's different in C++. C++ makes dereferencing `void *` invalid by restricting `*` to pointer-to-object and pointer-to-function types. The question is tagged both C and C++. I don't think it should be. This is the sort of confusion it causes. –  Feb 06 '16 at 12:19
  • @hvd I tagged it as both because it exists in both. Yes, `goto *void` is not valid C++, but that isn't the question. – Sarvadi Feb 06 '16 at 18:58
  • @Sarvadi Right, but you had no way of knowing whether "what does `goto *void` mean in C" and "what does `goto *void` mean in C++" would get the same answer. Even if it's invalid in both, and even if you already know it's invalid in both. –  Feb 06 '16 at 19:45
7

Seems to be a GCC bug. Here is a clang output as a comparison. It seems that these are the errors we should expect.

$ 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 source code:

int main(int argc, char const *argv[])
{
    int foo = 0;
    goto *foo;
}
Luis Chavier
  • 322
  • 1
  • 7
0

This is not a bug, rather a consequence of GCC's Labels and Values extension. I'm guessing they had things like fast jump tables and JIT's in mind. With this (mis)feature, you can jump into functions

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

and do wonderfully tasteful things like jump into a string literal

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

Try it online!

Don't forget that pointer arithmetic is permitted!

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");

I'll leave additional consequences as an exercise to the reader (argv[0], __FILE__, __DATE__, etc.)

Note that you'll need to ensure that you have executable permission for the area of memory to which you jump.

ceilingcat
  • 671
  • 7
  • 11
  • 2
    In fact it is a bug. It's documented to allow `goto *ptr;` where `ptr` is of type `void*`. `goto *foo;` where `foo` is of type `int*` violates gcc's own documentation of the extension. See [my answer](https://stackoverflow.com/a/35237062/827263) and [this bug report](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=32122). – Keith Thompson Mar 03 '18 at 03:16
  • @KeithThompson I see where the documentation specifies that the address of a label has type `void *` but where does it specify that non `void *` arguments of `goto *` are not allowed? – ceilingcat Mar 03 '18 at 03:21
  • 1
    "For example, `goto *ptr;` Any expression of type `void *` is allowed." – Keith Thompson Mar 03 '18 at 03:55