Что #define делает в компиляторе?

Я использую C чуть больше года, и мне было интересно, как именно работает #define. Я знаю, что вы можете использовать его как макрос, например. #define MUL(x, y) (x*y), но что происходит, когда ты даешь что-то, не давая определения? например

    #ifndef _SYNNOVESLIB_H
    #define _SYNNOVESLIB_H
    #endif

Помечается ли он просто как «определенный» или «истина» (как в 1) GCC или любым другим используемым компилятором?

Имя макроса затем помечается как определенное, с пустым списком замены. Вас интересует исходный код компилятора? Более интересными для пользователей являются конкретные правила языка C, например en.cppreference.com/w/c/preprocessor/replace. «Как» — вопрос неопределенный.

KamilCuk 02.07.2024 20:39

@KamilCuk да, точный исходный код компилятора был бы великолепен. Спасибо за список замены, это помогло!

synnøve 02.07.2024 20:50

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

0___________ 02.07.2024 20:57

Я имею в виду файл (или, что еще лучше, функцию), в котором он обрабатывает токен определения.

synnøve 02.07.2024 20:59

вы думаете, что это простая функция?

0___________ 02.07.2024 21:01

@0___________ черт возьми, нет, но я думаю, что смогу обрисовать, что происходит, чтобы понять, что оно делает

synnøve 02.07.2024 21:04

На самом деле он мало чем отличается от #define NAME x. В данном случае x — это пустая строка, поэтому при использовании макроса она заменяется пустой строкой.

Barmar 02.07.2024 21:21

Как вы думаете, почему вам нужен исходный код, чтобы понять такую ​​базовую операцию? Считаете ли вы, что пустые строки требуют какой-либо сложной обработки?

Barmar 02.07.2024 21:22

@Barmar Мне не нужен исходный код. Я просил об этом, потому что KamiiCuk сказал: «Вас интересует исходный код компилятора», и я неправильно истолковал это как предложение указать мне исходный код компилятора, в котором он определен. Я не думаю, что пустые строки также не требуют сложной обработки. Спасибо за первый комментарий, он очень помогает.

synnøve 02.07.2024 21:24

Я думаю, он спросил тебя об этом, потому что ты использовал тег compiler-construction. Это говорит о том, что вас интересует то, как компилятор это реализует, а не только то, как это используется и что это означает.

Barmar 02.07.2024 21:25

Меня интересует, как это реализует компилятор. Я знаю, как оно используется, и, узнав, что оно означает (на случай, если это все, что кто-то мог мне сообщить), я надеялся понять, что с ним делает компилятор.

synnøve 02.07.2024 21:28

Эта конкретная конструкция называется защитой включения и предназначена для предотвращения многократного #include повторения одного и того же заголовочного файла. Большинство компиляторов предоставляют #pragma once для достижения той же цели, но это не стандартно.

dan04 02.07.2024 21:32

Да, @ dan04, конкретный пример ОП выглядит как защита включения, но это ни в коем случае не единственное использование макросов с пустыми списками замены. Кажется, они заинтересованы в таких макросах в целом, а не только в тех, которые используются для защиты включения.

John Bollinger 02.07.2024 22:17

В контексте создания «защиты заголовка» ознакомьтесь с Создание собственного файла заголовка на C.

Lundin 03.07.2024 10:04
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
14
168
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

#define _SYNNOVESLIB_H сообщает компилятору заменить _SYNNOVESLIB_H ничем. Это будет верно для целей, которые проверяют, определен ли макрос, например defined, #ifdef и #ifndef.

Это также может быть полезно для условного определения определенных функций для совместимости. Вот пример на Perl 5.


6.10 Директивы предварительной обработки предоставляют грамматику для #define.

control-line:
  # define identifier replacement-list new-line

replacement-list: 
  pp-tokens(opt)

pp-tokens:
  preprocessing-token
  pp-tokens preprocessing-token

replacement-list — необязательный список токенов препроцессора, поэтому он может быть пустым.

Из 6.10.1 Условное включение

  1. Выражение, управляющее условным включением, должно быть целочисленным константным выражением, за исключением того, что: идентификаторы (включая те, которые лексически идентичны ключевым словам) интерпретируются, как описано ниже;169) и оно может содержать унарные операторные выражения вида

defined identifier

или

defined ( identifier )

которые оцениваются как 1, если идентификатор в настоящее время определен как имя макроса (то есть, если он предопределен или был предметом директивы предварительной обработки #define без промежуточной директивы #undef с тем же идентификатором субъекта), 0, если это не.

Обратите внимание, что значение не имеет значения, просто оно было определено.

  1. Директивы предварительной обработки форм

# ifdef identifier new-line groupopt

или

# ifndef identifier new-line groupopt

проверьте, определен ли идентификатор в настоящее время как имя макроса. Их условия эквивалентны #if defined identifier и #if !defined identifier соответственно.

То, как именно это обрабатывается внутри компилятора, зависит от компилятора, но подойдет и простая хэш-таблица. #define _SYNNOVESLIB_H добавит ключ _SYNNOVESLIB_H с нулевым или пустым значением. #ifndef _SYNNOVESLIB_H проверит, существует ли ключ в хеш-таблице, игнорируя значение. #undef _SYNNOVESLIB_H убирает ключ со стола.

Этот ответ можно прочитать как утверждение, что единственное применение макроса, определенного с пустым списком замен, — это проверка его определенности. Это совсем не так. Действительно, возможность проверки определенности макросов во многом ортогональна содержимому списков замены макросов.

John Bollinger 02.07.2024 22:25

@JohnBollinger Это не подразумевается, это просто объем вопроса.

Schwern 03.07.2024 04:40

Это не объем вопроса, как я его прочитал. Это просто природа конкретного примера, приведенного внутри. «Что происходит, когда вы определяете что-то, не давая определения? [...] Это просто помечается как «определенное» [...]?» -- НЕТ. Он определяется как макрос с указанным списком замены, который оказывается пустым.

John Bollinger 03.07.2024 15:17

@JohnBollinger Кажется, вы хотите обсудить тонкое различие между «пустым списком» и «нет значения». Технически вы правы, в лучшем случае правы, но вопрос не в этом. Теперь я понимаю, какой бит подразумевает, что определенность - единственный вариант использования, я смягчу это.

Schwern 04.07.2024 01:56

Не совсем. Меня не волнует, скажете ли вы «определено как ничего» или «определено как пустой список». Меня волнует, что вы неправильно говорите: «нет определения». В конечном счете, важным моментом является то, что определение макроса с пустым списком/без замены не является особым случаем, вопреки тому, что думает ФП.

John Bollinger 05.07.2024 00:27

@JohnBollinger Имейте в виду, что ОП использует C уже год.

Schwern 05.07.2024 02:46

@Schwern год - это количество времени, в течение которого я полностью свободно владею C, например. способен превратить (большинство) полностью реализованных идей проекта в код

synnøve 11.07.2024 23:07
Ответ принят как подходящий

Точный исходный код компилятора был бы замечательным.

#define обрабатывается в libcpp/directives.cc примерно https://github.com/gcc-mirror/gcc/blob/cc63b59e8843f049587b7a548a530f710085e577/libcpp/directives.cc#L652 . Определение сохранено в https://github.com/gcc-mirror/gcc/blob/master/libcpp/macro.cc#L3831. Внутренне макросы хранятся в хеш-таблице, но не нашел, где это происходит.

что происходит, когда ты даешь что-то, не давая определения?

Каждая допустимая директива #define связывает имя макроса с соответствующим списком замен («определением», в ваших терминах). Допускается пустой список замен, и с точки зрения препроцессора в этом нет ничего особенного. Вы не должны думать о таких случаях как о «без определения», потому что это введет вас в заблуждение — например, вы подумаете, что с этим связано какое-то особое поведение, или, возможно, предположите, что вы не можете расширить такой макрос, как вы это делаете. те, у которых есть другие виды списков замены. А еще потому, что выделение особого класса макросов как не имеющих определения делает вашу ментальную модель языка более сложной, чем она должна быть.

Помечается ли он просто как «определенный» или «истина» (как в 1) GCC или любым другим используемым компилятором?

Он определяется с помощью пустого списка замен, который полностью действителен и не имеет особого значения. Среди последствий — директивы препроцессора #ifdef и операторы defined будут распознавать макрос как определенный, как и любой другой определенный макрос. Как и любой другой макрос, он может быть расширен препроцессором, который заменяет имя макроса его (пустым) списком замены.

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

Например, чтобы избежать многократного определения одной и той же переменной, переменные , определенные в заголовках, обычно должны быть объявлены extern , однако должен быть один источник, в котором они не являются (явно) extern. Один из способов достижения этой цели (хотя я на самом деле не рекомендую его) выглядит примерно так:

заголовок.h

#ifndef EXTERN
#define EXTERN extern
#endif

// The following appearance of EXTERN will be expanded by the preprocessor:
EXTERN int my_variable;

Most_source_files.c

#include "header.h"

// gets:
// extern int my_variable;

one_special_source_file.c

#define EXTERN
#include "header.h"
#undef EXTERN

// gets:
//  int my_variable;

При этом один и тот же заголовок обычно предоставляет явные объявления extern, что необходимо в большинстве мест, но соответствующие неявные объявления extern в одном выбранном месте, где EXTERN определено как расширяющееся до нуля.

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

Schwern 04.07.2024 02:12

Нет, @Schwern, я подчеркиваю, что определение макроса, о котором спрашивает ОП, не является особым случаем. Видите жирный курсив? Он дает определение символа, какие бы слова вы ни использовали для этого, вопреки тому, что, по-видимому, предполагает ФП. Вся остальная семантика вытекает из этого. Что касается примеров использования, помимо проверки на определенность, я, конечно, мог бы их привести, но разве вы не настаивали в другом месте на строгом сосредоточении внимания на объеме вопроса?

John Bollinger 05.07.2024 00:41

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

Schwern 05.07.2024 02:58

Я бы не стал продавать короткий ОП, @Schwern, но, поскольку вы апеллируете к их предполагаемой неискушенности в отношении языка, я утверждаю, что «это не особый случай» имеет меньшую когнитивную нагрузку, чем «здесь есть специальные правила для этого». случай", а первый избегает замешательства, когда позже они обнаруживают случай, когда макрос, о котором они спрашивают, используется не только как флаг. Даже игнорируя второй из них, я просто не понимаю, почему вы предпочитаете описание особого случая. В любом случае, я привел пример, который, на мой взгляд, вполне доступен.

John Bollinger 05.07.2024 22:48

Спасибо за пример, и ваш ответ намного лучше. Думаю, я вижу различие, которое вы проводите: «EXTERN определяется как пустой список [чего?], который ничем не расширяется». Тогда как «EXTERN определяется как ничто и ни к чему не расширяется» подразумевает особый случай. Но для этого необходимо знать, что это список токенов (и что «ничего» и «пустой список» — это разные вещи), а это требует знания грамматики. Если вы это уже знаете, вы, вероятно, не зрители. «Ничто не превратится в ничто» — это естественное предположение. Это методика обучения, при которой сложность вводится постепенно, а не сразу.

Schwern 05.07.2024 23:42

@Schwern, не могли бы вы немного рассказать об этой сложности, пожалуйста? Приносим извинения за поздний ответ. Я выбрал C в качестве основного языка (хотя при необходимости могу использовать язык более высокого уровня, например Java), и после ознакомления с тем, как все работает, я хочу узнать больше о том, как это работает. Боюсь, мне не удалось найти конкретные детали достаточно хорошо, поэтому я создал этот пост, чтобы найти больше. Кстати, ваш ответ помог, спасибо за это.

synnøve 10.07.2024 07:43

@synnøve Например, можно сказать, что #define this x.y означает замену this на x.y, а #define this означает замену this ничем. Это короткое, понятное и полезное объяснение. Технически правильное объяснение заключается в том, что #define this x.y означает замену this списком замен токенов препроцессора x и . и y, тогда как #define this означает замену this пустым списком токенов препроцессора. Это сложнее понять, и возникают новые темы: что такое токен препроцессора? что значит заменить что-то пустым списком?

Schwern 11.07.2024 00:59

@synnøve Изучение C очень полезно, вы узнаете много нового о том, как на самом деле работают компьютеры. Однако, если вы только учитесь программировать, я бы посоветовал вам одновременно изучать язык более высокого уровня. C очень старый и, по современным меркам, довольно плохой язык (за это меня ждет ад), полный ловушек и подводных камней и требующий изучения многих вещей одновременно. Если вам нужен быстрый современный язык системного программирования, рассмотрите Rust или Go.

Schwern 11.07.2024 01:04

Другие вопросы по теме