32bit_me (32bit_me) wrote,
32bit_me
32bit_me

Category:

Бесполезные знания по С

Если вы занимаетесь тем, что проводите собеседования кандидатов на позицию программиста, и хотите завалить как можно больше народа, чтобы поднять своё ЧСВ, то вот вам шикарный способ.



Проверенный способ, спрашивать наизусть триграфы постепенно выходит из моды, хотя напрасно, последний раз триграфы были нужны на практике в 70-х годах (объясните, для чего), и их реально мало кто знает.

Но язык Си богат на странные конструкции. Например, переменные-метки. Это, конечно, не стандартный С, а расширенный стандарт gcc, но clang его тоже поддерживает, так что всё норм. И icc поддерживает. MSVC не поддерживает, но кого волнует этот малораспространённый, никому не нужный компилятор малоизвестной фирмы.

Рассмотрим пример.

#include <stdio.h>

int main ()
{
  void *var_label = &&label;
  goto *var_label;
  printf ("1\n");
label:
  printf ("2\n");
}


В этом коде переменной var_label присваивается адрес метки, а потом выполняется переход по этому адресу. Пример, конечно, дурацкий, потому что можно сделать просто goto label.

Но переменные - метки можно передавать в функции. Рассмотрим пример:

#include <stdio.h>

void bar (void *label)
{
  goto *label;
}

int main ()
{
  void *var_label = &&label;
  bar(var_label);
  printf ("1\n");
label:
  printf ("2\n");
}


Попробуйте сказать, не запуская программы, что она напечатает. Скорее всего, ответ вас удивит. Хотя, это сработает только для gcc -O0. Для gcc -O1 и выше ответом будет "Ошибка сегментирования", clang это вообще не скомпилирует, а icc скомпилирует, но что будет в результате, проверяйте сами, у меня лапки.
Программа, скомпилированная gcc -O0 напечатает:

2
1
2

Почему? Вызов bar выполнит переход на label в main, далее выполнит возврат в точку возврата bar (для того, чтобы представить это, нужно несколько расширить чертоги разума), а затем уже напечатает 1 и 2.

Итак, первое реальное применение переменных-меток - обфускация программы.

Но есть и более полезное применение переменных-меток.

#include <stdio.h>

int foo(int x)
{
  void *labels[] = {&&label1, &&label2, &&label3};
  goto *labels[x];
label1:
  return 100;
label2:
  return 200;
label3:
  return 300;
}

int main ()
{
  printf("%d\n", foo(1));
}


Нетривиальная замена конструкции switch, например.

По сути, переменная-метка, это просто указатель, и к ней применима арифметика указателей. Например, что напечатает такая программа?

#include <stdio.h>

int foo(int x)
{
  void *labels[] = {&&label1, &&label2, &&label3};
  goto *labels[x];
label1:
  return 100;
label2:
  return (long)&&label1 - (long)&&label3;
label3:
  return 300;
}

int main ()
{
  printf("%d\n", foo(1));
}



А такая? Объясните, почему?

#include <stdio.h>

int foo(int x)
{
  void *labels[] = {&&label1, &&label2, &&label3};
  return (long)&&label1 - (long)&&label2;
label1:
  return 100;
label2:
  return 200;
label3:
  return 300;
}

int main ()
{
  printf("%d\n", foo(1));
}


Ответы здесь просты. Посмотрим, как компилируется последний пример:

foo:
.LFB0:
  .cfi_startproc
.L2:
.L3:
.L4:
  movl $.L4, %eax
  subq $.L3, %rax
  ret


Компилятор выбросил весь недостижимый код, поэтому L3 и L4 равны, и программа напечатает 0. Но обратите внимание, что результат вычисляется в рантайме:

  movl $.L4, %eax
  subq $.L3, %rax


хотя, казалось бы, адреса меток присваиваются при компиляции. Но на самом деле, нет. Они присваиваются линкером, кодогенератор (а, тем более, фронтенд) их ещё не знает, и не может вычислить константные значения. Вы могли бы заметить, что в данном случае неважно, какие адреса будут у меток, если они равны, то результат их вычитания в любом случае будет 0, но, вероятно, разработчики компилятора не позаботились об оптимизации столь практически значимой конструкции

Вот так. Надеюсь, вам было интересно узнать что-то новое, хотя и совершенно бесполезное.
Tags: #include, C и C++
Subscribe

  • Ретрожурнал

    Удивительно, но во времена ссср издавался американский журнал на русском языке "Электроника" ("Electronics"). Переводом занималось издательство…

  • Выниматели микросхем

    Продаётся на барахолке такое чудо: Спрашивается, нахрена и кому понадобились микросхемы с этой платы? Драгметаллов в них нет, вставить их куда-то…

  • Реальная история разработки Commodore C128

    Опубликовал на хабре новый перевод: Реальная история разработки Commodore C128. Заходите, ставьте плюсики, комментируйте там или здесь.

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 35 comments

  • Ретрожурнал

    Удивительно, но во времена ссср издавался американский журнал на русском языке "Электроника" ("Electronics"). Переводом занималось издательство…

  • Выниматели микросхем

    Продаётся на барахолке такое чудо: Спрашивается, нахрена и кому понадобились микросхемы с этой платы? Драгметаллов в них нет, вставить их куда-то…

  • Реальная история разработки Commodore C128

    Опубликовал на хабре новый перевод: Реальная история разработки Commodore C128. Заходите, ставьте плюсики, комментируйте там или здесь.