Подтвердить что ты не робот

Можно ли сохранить адрес метки в переменной и использовать goto для перехода к ней?

Я знаю, что все ненавидят gotos. В моем коде по причинам, которые я рассмотрел и им комфортно, они обеспечивают эффективное решение (т.е. я не ищу "не делаю этого" в качестве ответа, я понимаю ваши оговорки и понимаю, почему я их использую так или иначе).

До сих пор они были фантастическими, но я хочу расширить функциональные возможности таким образом, чтобы я по существу мог хранить указатели на ярлыках, а затем переходить к ним позже.

Если этот код работал, он будет представлять собой тип функциональности, который мне нужен. Но это не сработает, и 30 минут поиска не обнаружили ничего. У кого-нибудь есть идеи?

int main (void)
{
  int i=1;
  void* the_label_pointer;

  the_label:

  the_label_pointer = &the_label;

  if( i-- )
    goto *the_label_pointer;

  return 0;
}
4b9b3361

Ответ 1

Стандарты C и С++ не поддерживают эту функцию. Тем не менее, сборник компиляторов GNU (GCC) включает в себя нестандартное расширение для этого, как описано в в этой статье. По существу, они добавили специальный оператор "& &" который сообщает адрес метки как тип "void *". Подробнее см. В статье.

P.S. Другими словами, просто используйте "& &" вместо "&" в вашем примере, и он будет работать на GCC.
P.P.S. Я знаю, что вы не хотите, чтобы я это сказал, но я все равно скажу... НЕ ДЕЛАЙТЕ ЭТО!!!

Ответ 2

Вы можете сделать что-то подобное с setjmp/longjmp.

int main (void)
{
    jmp_buf buf;
    int i=1;

    // this acts sort of like a dynamic label
    setjmp(buf);

    if( i-- )
        // and this effectively does a goto to the dynamic label
        longjmp(buf, 1);

    return 0;
}

Ответ 3

В соответствии со стандартом C99, § 6.8.6, синтаксис для goto:

    goto identifier ;

Итак, даже если вы можете взять адрес метки, вы не сможете использовать его с goto.

Вы можете объединить goto с switch, который похож на вычисленный goto, для аналогичного эффекта:

int foo() {
    static int i=0;
    return i++;
}

int main(void) {
    enum {
        skip=-1,
        run,
        jump,
        scamper
    } label = skip; 

#define STATE(lbl) case lbl: puts(#lbl); break
    computeGoto:
    switch (label) {
    case skip: break;
        STATE(run);
        STATE(jump);
        STATE(scamper);
    default:
        printf("Unknown state: %d\n", label);
        exit(0);
    }
#undef STATE
    label = foo();
    goto computeGoto;
}

Если вы используете это для чего-то другого, кроме запутанного конкурса C, я буду охотиться на вас и причинить вам вред.

Ответ 4

Оператор switch ... case представляет собой вычисленный goto. Хорошим примером того, как это работает, является причудливый хак, известный как Duff Device:

send(to, from, count)
register short *to, *from;
register count;
{
    register n=(count+7)/8;
    switch(count%8){
    case 0: do{ *to = *from++;
    case 7:     *to = *from++;
    case 6:     *to = *from++;
    case 5:     *to = *from++;
    case 4:     *to = *from++;
    case 3:     *to = *from++;
    case 2:     *to = *from++;
    case 1:     *to = *from++;
        }while(--n>0);
    }
}

Вы не можете сделать goto из произвольного местоположения, используя эту технику, но вы можете обернуть всю свою функцию в инструкции switch на основе переменной, затем установите эту переменную, указывающую, куда вы хотите пойти, и goto, что оператор switch.

int main () {
  int label = 0;
  dispatch: switch (label) {
  case 0:
    label = some_computation();
    goto dispatch;
  case 1:
    label = another_computation();
    goto dispatch;
  case 2:
    return 0;
  }
}

Конечно, если вы это сделаете много, вам нужно написать несколько макросов, чтобы обернуть его.

Этот метод наряду с некоторыми макросами удобства может быть даже использован для реализации сопрограмм в C.

Ответ 5

В очень очень старой версии языка C (подумайте о времени, когда динозавры бродили по Земле), известной как "Справочное руководство C" (которое относится к , написанный Деннисом Ритчи), у лейблов формально был тип "массив int" (странный, но истинный), что означает, что вы можете объявить переменную int *

int *target;

и присвоить адрес метки этой переменной

target = label; /* where `label` is some label */

Позже вы могли бы использовать эту переменную в качестве операнда оператора goto

goto target; /* jumps to label `label` */

Однако в ANSI C эта функция была выбрана. В стандартном современном C вы не можете принять адрес метки, и вы не можете выполнить "параметризованный" goto. Предполагается, что это поведение должно быть смоделировано с помощью операторов switch, указателей на функции и других методов и т.д. На самом деле даже в "Справочном руководстве по C" сказано, что "Переменные метки - это плохая идея в целом, оператор switch делает их почти всегда ненужно" (см. "14.4 Ярлыки" ).

Ответ 6

Я знаю это чувство, тогда все говорят, что этого не следует делать; это просто нужно сделать. В GNU C используйте &&the_label;, чтобы взять адрес метки. (https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html) Синтаксис, который вы догадались, goto *ptr на void*, фактически является тем, что использует GNU C.

Или, если вы хотите по какой-то причине использовать встроенную сборку, вот как это сделать с помощью GNU C asm goto

// unsafe: this needs to use  asm goto so the compiler knows
// execution might not come out the other side
#define unsafe_jumpto(a) asm("jmp *%0"::"r"(a):)

// target pointer, possible targets
#define jumpto(a, ...) asm goto("jmp *%0" : : "r"(a) : : __VA_ARGS__)

int main (void)
{
  int i=1;
  void* the_label_pointer;

  the_label:
  the_label_pointer = &&the_label;

label2:

  if( i-- )
    jumpto(the_label_pointer, the_label, label2, label3);

label3:
  return 0;
}

Список меток должен включать все возможные значения для the_label_pointer.

Расширение макроса будет выглядеть примерно так:

asm goto("jmp *%0" : : "ri"(the_label_pointer) : : the_label, label2, label3);

Это компилируется с gcc 4.5 и более поздними версиями, а также с последним clang, который только что получил поддержку asm goto через некоторое время после clang 8.0. https://godbolt.org/z/BzhckE. Результирующий asm выглядит следующим образом для GCC9.1, который оптимизировал "петлю" из i=i/i-- и просто поместил the_label после jumpto. Таким образом, он все еще работает ровно один раз, как в исходном коде C.

# gcc9.1 -O3 -fpie
main:
    leaq    .L2(%rip), %rax     # ptr = &&label
    jmp *%rax                     # from inline asm
.L2:
    xorl    %eax, %eax          # return 0
    ret

Но Clang не сделал эту оптимизацию и все еще имеет цикл:

# clang -O3 -fpie
main:
    movl    $1, %eax
    leaq    .Ltmp1(%rip), %rcx
.Ltmp1:                                 # Block address taken
    subl    $1, %eax
    jb      .LBB0_4                  # jump over the JMP if i was < 1 (unsigned) before SUB.  i.e. skip the backwards jump if i wrapped
    jmpq    *%rcx                   # from inline asm
.LBB0_4:
    xorl    %eax, %eax              # return 0
    retq

Оператор адреса метки & будет работать только с gcc. И, очевидно, макрос сборки jumpto должен быть реализован специально для каждого процессора (этот работает как с 32-, так и с 64-битным x86).

Также имейте в виду, что (без asm goto) не было бы никакой гарантии, что состояние стека одинаково в двух разных точках одной и той же функции. И, по крайней мере, при включенной оптимизации возможно, что компилятор предполагает, что некоторые регистры содержат некоторое значение в точке после метки. Подобные вещи могут быть легко испорчены, тогда компилятор не ожидает. Обязательно проверяйте прочитанный скомпилированный код.

Вот почему asm goto необходим для того, чтобы сделать его безопасным, сообщив компилятору, куда вы/могли бы перейти, получив согласованный код для перехода и назначения.

Ответ 7

Отмечу, что функционально описанное здесь (включая && gcc) является IDEAL для реализации интерпретатора языка Forth в C. Это удаляет все "не делайте этого" аргументы из воды - подходящий между этой функциональностью и тем, как работает внутренний интерпретатор Forth, слишком хорошо, чтобы игнорировать.

Ответ 8

Используйте указатели на функции и цикл while. Не делайте код, кому-то еще придется сожалеть об исправлении для вас.

Я предполагаю, что вы пытаетесь каким-то образом изменить адрес ярлыка. Указатели функций будут работать.

Ответ 9

#include <stdio.h>

int main(void) {

  void *fns[3] = {&&one, &&two, &&three};   
  char p;

  p = -1;

  goto start; end:   return 0;     
  start:   p++;   
  goto *fns[p];
  one:  printf("hello ");  
  goto start;  
  two:  printf("World. \n");  
  goto start;
  three:  goto end;
}

Ответ 10

Единственная официально поддерживаемая вещь, которую вы можете сделать с меткой на C, - это goto it. Как вы заметили, вы не можете взять его адрес или сохранить его в переменной или что-то еще. Поэтому вместо того, чтобы говорить "не делай этого", я собираюсь сказать "ты не можешь этого сделать".

Похоже, вам нужно будет найти другое решение. Возможно, язык ассемблера, если это критически важно для производительности?

Ответ 11

Прочтите это: setjmp.h - Википедия Как уже говорилось, это возможно с помощью setjmp/longjmp, с помощью которого вы можете сохранить точку перехода в переменной и вернуться назад.

Ответ 12

Вы можете назначить метку переменной, используя & &. Вот ваш модифицированный код.


int main (void)
{
  int i=1;
  void* the_label_pointer = &&the_label;

  the_label:


  if( i-- )
    goto *the_label_pointer;


  return 0;
}

Ответ 13

Согласно этот поток, метки не являются стандартными, поэтому независимо от того, будут они работать или нет, зависит от используемого вами компилятора.

Ответ 14

Вы можете сделать что-то вроде компьютера Фортран с указателями на функции.

// global variables up here

void c1(){ // chunk of code

}

void c2(){ // chunk of code

}

void c3(){
// chunk of code

}

void (*goTo[3])(void) = {c1, c2, c3};

// then
int x = 0;

goTo[x++] ();

goTo[x++] ();

goTo[x++] ();