Есть ли какие-либо гарантии согласованности директив __LINE__?

GCC 9 недавно изменил поведение директивы __LINE__ в некоторых случаях. Программа ниже иллюстрирует изменение:

#include <stdio.h>
#define expand() __LINE__
int main() {
  printf("%d\n",expand(
                ));
  return 0;
}

Поскольку макрос expand() (который расширяется до __LINE__) охватывает более одной строки, GCC до 8.3 (и Clang до 8.0) считает номер последней строки раскрытия, выводя 5. Но GCC 9 учитывает первую строку и печатает 4.

(Ссылка Godbolt: https://godbolt.org/z/3Nk2al)

Стандарт C11 не очень точно описывает точное поведение __LINE__, за исключением:

6.10.8 Стандартные имена макросов

Значения предопределенных макросов, перечисленных в следующих подпунктах (кроме __FILE__ и __LINE__), остаются постоянными во всей единице перевода.

(...)

6.8.10.1 Обязательные макросы

Следующие имена макросов должны быть определены реализацией:

(...)

__LINE__ Предполагаемый номер строки (в текущем исходном файле) текущей исходной строки (целочисленная константа).

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

Например, можно ли утверждать, что предполагаемый номер строки текущей исходной строки должен быть стабильным, пока сам исходный текст не изменился?


person anol    schedule 05.06.2019    source источник
comment
Интересно. способы, которыми я использовал __LINE__ в прошлом, будут нечувствительны к этому изменению (мне иногда нужна была удобочитаемость для человека (а люди гибки) и гарантия того, что указанные номера строк имеют правильные отношения последовательности (который должен быть сохранен при этом изменении)). Это что-то ломает?   -  person dmckee --- ex-moderator kitten    schedule 05.06.2019
comment
Взгляните на вывод препроцессора (используя параметр -E), чтобы увидеть результаты подстановки макроса. При использовании компилятора, который я использую, printf заканчивается одной строкой, включая завершающий );   -  person user3386109    schedule 05.06.2019
comment
@dmckee Это не было бы вопросом language-lawyer, если бы у него был реальный вариант использования ;-)   -  person Philip Kendall    schedule 05.06.2019
comment
@dmckee у нас есть несколько тестовых оракулов, которые хранят предварительно обработанный код C, а программное обеспечение, такое как Hiredis, содержит несколько assert, занимающих несколько строк. Из-за этого изменения разработчики со старыми версиями GCC или использующие Clang получат разные оракулы, и я заметил разницу.   -  person anol    schedule 06.06.2019


Ответы (1)


Хотя в общем случае найти номер строки из конкретной инструкции сложно, то есть GDB пытается найти номер строки из некоторого кода, который потерпел крах, инструкция printf __LINE__ относительно проста, поскольку компилятор генерирует номер как статический для конкретное место.

Сам стандарт C11 просто говорит, что номер строки не должен изменяться, пока вы находитесь в макросе, т.е. __LINE__ должен отражать, где вы находитесь в программе после того, как макрос был расширен, а не в какой строке кода находится макрос. Это позволяет вам делать такие вещи, как предоставление номера строки вызываемого объекта вашей функции, путем создания макроса, который выводит номер строки и затем вызывает вашу функцию. Например:

#define f(x) ({ printf("called f(x) at line=%d\n", __LINE__); f__real(x); })

Что касается точного номера строки, который представлен, он зависит от компилятора и не является частью какого-либо стандарта. Т.е. ваш пример можно переписать как:

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

и в этом случае действительным ответом может быть 2 или 3.

Если вы пытаетесь определить номер строки динамически, это предоставляется через системные библиотеки обратной трассировки. то есть backtrace () в Linux.

person Clarus    schedule 05.06.2019