Почему препроцессор не приводит к тому, что два соседних знака минус являются декрементом?

Рассмотрим следующий код:

#include <stdio.h>
#define A -B
#define B -C
#define C 5

int main()
{
  printf("The value of A is %d\n", A); 
  return 0;
}

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

  • сначала A следует заменить на -B
  • тогда B следует заменить на -C, таким образом выражение, выраженное в - C
  • тогда C следует заменить на 5, таким образом, выражение, выраженное в - 5

Таким образом, полученное выражение должно давать ошибку компиляции (ошибка lvalue). Но правильный ответ - 5, как выход может быть 5?

Пожалуйста, помогите мне в этом.

Ответ 1

Это препроцессы до (обратите внимание на пробел):

int main()
{
  printf("The value of A is %d\n", - -5);
  return 0;
}

Препроцессор вставляет жетоны, а не строки. Он не будет создавать -- из двух смежных маркеров -, если вы не принудительно объединяете токены с ##:

#define CAT_(A,B) A##B
#define CAT(A,B) CAT_(A,B)

#define A CAT(-,B)

#define B -C
#define C 5

int main()
{
  printf("The value of A is %d\n", A); /* A is --5 here—no space */ 
  return 0;
}

Ответ 2

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

Перед запуском препроцессора исходный файл разбивается на токены предварительной обработки, которые являются отдельными единицами текста. Например, один знак минус обрабатывается не как символ, а как знак, состоящий из знака минус, а знак двойного минуса рассматривается как токен, состоящий из двух знаков минус.

Препроцессор C запускает и заменяет каждый макрос не литеральным текстом замены макроса, а скорее серией токенов препроцессора в этой замене. В этом случае препроцессор заменяет A минусом, за которым следует B, затем заменяет B минусом, за которым следует C, а затем заменяет C на 5. Эффект здесь состоит в том, что есть два унарных минуса, примененных к 5, а не декремент оператора, хотя литеральный поиск и замена сгенерировали бы оператор декремента, создающий синтаксическую ошибку.

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

Теперь в разделе legalise: section & sect; 5.1.1.2/7 говорится, что после макроподстановки каждый токен предварительной обработки - и здесь есть два из них (два минусовых знака) - преобразуются в фактические токены, а затем theyre синтаксически и семантически анализируется. Это означает, что у theres нет возможности для компилятора пересканировать эти жетоны, чтобы переосмыслить их как один токен. Так что это странный случай, когда результирующий поток токена не может быть введен в исходный код без изменения значения.

Ответ 3

Вспомните результирующее выражение как это:

-(-(5))