Скажите gcc, что вызов функции не вернется

Я использую C99 под GCC.

У меня есть функция, объявленная static inline в заголовке, который я не могу изменить.

Функция никогда не возвращается, но не помечена __attribute__((noreturn)).

Как я могу вызвать функцию таким образом, чтобы сообщить компилятору, что он не вернется?

Я вызываю его из своей собственной функции noreturn и частично хочу подавить предупреждение "noreturn function returns", но также хочу помочь оптимизатору и т.д.

Я попытался включить объявление с атрибутом, но получаю предупреждение о повторном объявлении.

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

Ответы

Ответ 1

Из функции, которую вы определили и которая вызывает внешнюю функцию, добавьте вызов к __builtin_unreachable который встроен как минимум в компиляторы GCC и Clang и помечен как noreturn. Фактически эта функция больше ничего не делает и не должна вызываться. Только здесь, чтобы компилятор мог сделать вывод, что выполнение программы остановится на этом этапе.

static inline external_function() // lacks the noreturn attribute
{ /* does not return */ }

void your_function() __attribute__((noreturn)) {
    external_function();     // the compiler thinks execution may continue ...
    __builtin_unreachable(); // ... and now it knows it won't go beyond here
}

Изменить: просто чтобы прояснить несколько моментов, поднятых в комментариях, и, как правило, дать немного контекста:

  • У функции есть только два способа не возвращаться: цикл навсегда или короткое замыкание в обычном потоке управления (например, выброс исключения, выпадение из функции, завершение процесса и т.д.)
  • В некоторых случаях компилятор может вывести и доказать с помощью статического анализа, что функция не вернется. Даже теоретически это не всегда возможно, и поскольку мы хотим, чтобы компиляторы работали быстро, обнаруживаются только очевидные/простые случаи.
  • __attribute__((noreturn)) - это аннотация (например, const), которая позволяет программисту сообщить компилятору, что он абсолютно уверен, что функция не вернется. Следуя принципу доверия, но проверки, компилятор пытается доказать, что функция действительно не возвращает. If может затем выдать ошибку, если она докажет, что функция может вернуться, или предупреждение, если она не смогла доказать, возвращается ли функция или нет.
  • __builtin_unreachable имеет неопределенное поведение, потому что он не предназначен для вызова. Это только помогло статическому анализу компилятора. Действительно, компилятор знает, что эта функция не возвращает, так что любой следующий код доказуемо недоступен (кроме как через переход).

После того, как компилятор установил (либо сам, либо с помощью программиста), что некоторый код недоступен, он может использовать эту информацию для оптимизации, подобной этой:

  • Удалите шаблонный код, используемый для возврата из функции к вызывающей стороне, если функция никогда не возвращается
  • Распространение информации о недоступности, т.е. Если единственный путь выполнения к кодовым точкам лежит через недоступный код, то этот пункт также недоступен. Примеры:
    • если функция не возвращает, любой код, следующий за ее вызовом и недоступный через переходы, также недоступен. Пример: код, следующий за __builtin_unreachable(), недоступен.
    • в частности, если единственный путь к возврату функции лежит через недоступный код, функция может быть помечена как noreturn. Вот что происходит с your_function.
    • любая ячейка памяти/переменная, используемая только в недоступном коде, не требуется, поэтому настройки/вычисления содержимого таких данных не нужны.
    • любые вычисления, которые, вероятно, (1) не нужны (предыдущий пункт) и (2) не имеют побочных эффектов (например, pure функции), могут быть удалены.

Иллюстрация: - Невозможно удалить вызов external_function поскольку он может иметь побочные эффекты. На самом деле, это, по крайней мере, побочный эффект прекращения процесса! - Возвратная котельная плита your_function может быть удалена

Вот еще один пример, показывающий, как можно удалить код до недоступной точки

int compute(int) __attribute((pure)) { return /* expensive compute */ }
if(condition) {
    int x = compute(input); // (1) no side effect => keep if x is used
                            // (8) x is not used  => remove
    printf("hello ");       // (2) reachable + side effect => keep
    your_function();        // (3) reachable + side effect => keep
                            // (4) unreachable beyond this point
    printf("word!\n");      // (5) unreachable => remove
    printf("%d\n", x);      // (6) unreachable => remove
                            // (7) mark 'x' as unused
} else {
                            // follows unreachable code, but can jump here
                            // from reachable code, so this is reachable
   do_stuff();              // keep
}

Ответ 2

Несколько решений:

повторное выделение вашей функции с помощью __attribute__

Вы должны попытаться изменить эту функцию в ее заголовке, добавив к ней __attribute__((noreturn)).

Вы можете переопределить некоторые функции с новым атрибутом, как показывает этот тупой тест (добавление атрибута в fopen):

 #include <stdio.h>

 extern FILE *fopen (const char *__restrict __filename,
            const char *__restrict __modes)
   __attribute__ ((warning ("fopen is used")));

 void
 show_map_without_care (void)
 {
   FILE *f = fopen ("/proc/self/maps", "r");
   do
     {
       char lin[64];
       fgets (lin, sizeof (lin), f);
       fputs (lin, stdout);
     }
   while (!feof (f));
   fclose (f);
 }

переопределение с помощью макроса

Наконец, вы можете определить макрос как

#define func(A) {func(A); __builtin_unreachable();}

(при этом используется тот факт, что внутри макроса имя макроса не раскрывается).

Если никогда не возвращается func декларирует как возвращение например int вы будете использовать выражение заявления как

#define func(A) ({func(A); __builtin_unreachable(); (int)0; })

Решения на основе макросов, такие как выше, не всегда будут работать, например, если func передается как указатель на функцию, или просто если какой-то парень кодирует (func)(1) что допустимо, но безобразно.


перераспределение статического inline с атрибутом noreturn

И следующий пример:

 // file ex.c
 // declare exit without any standard header
 void exit (int);

 // define myexit as a static inline
 static inline void
 myexit (int c)
 {
   exit (c);
 }

 // redeclare it as notreturn
 static inline void myexit (int c) __attribute__ ((noreturn));

 int
 foo (int *p)
 {
   if (!p)
     myexit (1);
   if (p)
     return *p + 2;
   return 0;
 }

при компиляции с GCC 4.9 (из Debian/Sid/x86-64), поскольку gcc -S -fverbose-asm -O2 ex.c) дает файл сборки, содержащий ожидаемую оптимизацию:

         .type   foo, @function
 foo:
 .LFB1:
    .cfi_startproc
    testq   %rdi, %rdi      # p
    je      .L5     #,
    movl    (%rdi), %eax    # *p_2(D), *p_2(D)
    addl    $2, %eax        #, D.1768
    ret
.L5:
    pushq   %rax    #
    .cfi_def_cfa_offset 16
    movb    $1, %dil        #,
    call    exit    #
    .cfi_endproc
 .LFE1:
    .size   foo, .-foo

Вы можете поиграть с диагностикой #pragma GCC, чтобы выборочно отключить предупреждение.


Настройка GCC с MELT

Наконец, вы можете настроить свой недавний gcc используя плагин MELT и кодируя ваше простое расширение (на языке, специфичном для домена MELT), чтобы добавить атрибут noreturn при noreturn нужной функции. Это, вероятно, дюжина строк MELT, использующих register_finish_decl_first и соответствие имени функции.

Поскольку я являюсь основным автором MELT (свободного программного обеспечения GPLv3+), я мог бы даже написать его для вас, если вы спросите, например, здесь или, предпочтительно, на [email protected] ;дайте конкретное имя вашей никогда не возвращающейся функции.

Вероятно, MELT-код выглядит так:

  ;;file your_melt_mode.melt
  (module_is_gpl_compatible "GPLv3+")
  (defun my_finish_decl (decl)
     (let ( (tdecl (unbox :tree decl))
       )
     (match tdecl
        (?(tree_function_decl_named
            ?(tree_identifier ?(cstring_same "your_function_name")))
          ;;; code to add the noreturn attribute
          ;;; ....
        ))))
  (register_finish_decl_first my_finish_decl)

Настоящий код MELT немного сложнее.Вы хотите определить your_adding_attr_mode там.Спроси меня больше.

После того, как вы закодированы ваше Растопить расширение your_melt_mode.melt для ваших потребностей (и компилируетесь, что MELT расширения в your_melt_mode.quicklybuilt.so, как документированы в расплавленных учебниках), вы будете компилировать код

  gcc -fplugin=melt \
      -fplugin-arg-melt-extra=your_melt_mode.quicklybuilt \
      -fplugin-arg-melt-mode=your_adding_attr_mode \
      -O2 -I/your/include -c yourfile.c

Другими словами, вы просто добавляете несколько флагов -fplugin-* к своим CFLAGS в вашем Makefile !

Кстати, я просто кодирую в мониторе MELT (на github: https://github.com/bstarynk/melt-monitor..., файл meltmom-process.melt что meltmom-process.melt то очень похожее.

С расширением MELT вы не получите никаких дополнительных предупреждений, поскольку расширение MELT изменит внутреннее GCC AST (дерево GCC) объявленной функции на лету!

Настройка GCC с помощью MELT, вероятно, является наиболее пуленепробиваемым решением, поскольку оно модифицирует внутренний AST GCC. Конечно, это, вероятно, самое дорогое решение (и оно специфично для GCC и может потребовать изменений -S mall- при развитии GCC, например, при использовании следующей версии GCC), но, как я пытаюсь показать, это довольно легко в вашем случае.

PS. В 2019 году GCC MELT является заброшенным проектом. Если вы хотите настроить GCC (для любой последней версии GCC, например, GCC 7, 8 или 9), вам нужно написать свой собственный плагин GCC в C++.