Я использую C чуть больше года, и мне было интересно, как именно работает #define
.
Я знаю, что вы можете использовать его как макрос, например. #define MUL(x, y) (x*y)
, но что происходит, когда ты даешь что-то, не давая определения?
например
#ifndef _SYNNOVESLIB_H
#define _SYNNOVESLIB_H
#endif
Помечается ли он просто как «определенный» или «истина» (как в 1) GCC или любым другим используемым компилятором?
@KamilCuk да, точный исходный код компилятора был бы великолепен. Спасибо за список замены, это помогло!
«Было бы здорово получить точный исходный код компилятора». нет, это будет совершенно бесполезно. Он не покажет ничего, связанного с вашим вопросом.
Я имею в виду файл (или, что еще лучше, функцию), в котором он обрабатывает токен определения.
вы думаете, что это простая функция?
@0___________ черт возьми, нет, но я думаю, что смогу обрисовать, что происходит, чтобы понять, что оно делает
На самом деле он мало чем отличается от #define NAME x
. В данном случае x
— это пустая строка, поэтому при использовании макроса она заменяется пустой строкой.
Как вы думаете, почему вам нужен исходный код, чтобы понять такую базовую операцию? Считаете ли вы, что пустые строки требуют какой-либо сложной обработки?
@Barmar Мне не нужен исходный код. Я просил об этом, потому что KamiiCuk сказал: «Вас интересует исходный код компилятора», и я неправильно истолковал это как предложение указать мне исходный код компилятора, в котором он определен. Я не думаю, что пустые строки также не требуют сложной обработки. Спасибо за первый комментарий, он очень помогает.
Я думаю, он спросил тебя об этом, потому что ты использовал тег compiler-construction
. Это говорит о том, что вас интересует то, как компилятор это реализует, а не только то, как это используется и что это означает.
Меня интересует, как это реализует компилятор. Я знаю, как оно используется, и, узнав, что оно означает (на случай, если это все, что кто-то мог мне сообщить), я надеялся понять, что с ним делает компилятор.
Эта конкретная конструкция называется защитой включения и предназначена для предотвращения многократного #include
повторения одного и того же заголовочного файла. Большинство компиляторов предоставляют #pragma once
для достижения той же цели, но это не стандартно.
Да, @ dan04, конкретный пример ОП выглядит как защита включения, но это ни в коем случае не единственное использование макросов с пустыми списками замены. Кажется, они заинтересованы в таких макросах в целом, а не только в тех, которые используются для защиты включения.
В контексте создания «защиты заголовка» ознакомьтесь с Создание собственного файла заголовка на C.
#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 Условное включение
- Выражение, управляющее условным включением, должно быть целочисленным константным выражением, за исключением того, что: идентификаторы (включая те, которые лексически идентичны ключевым словам) интерпретируются, как описано ниже;169) и оно может содержать унарные операторные выражения вида
defined identifier
или
defined ( identifier )
которые оцениваются как 1, если идентификатор в настоящее время определен как имя макроса (то есть, если он предопределен или был предметом директивы предварительной обработки #define без промежуточной директивы #undef с тем же идентификатором субъекта), 0, если это не.
Обратите внимание, что значение не имеет значения, просто оно было определено.
- Директивы предварительной обработки форм
# 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
убирает ключ со стола.
Этот ответ можно прочитать как утверждение, что единственное применение макроса, определенного с пустым списком замен, — это проверка его определенности. Это совсем не так. Действительно, возможность проверки определенности макросов во многом ортогональна содержимому списков замены макросов.
@JohnBollinger Это не подразумевается, это просто объем вопроса.
Это не объем вопроса, как я его прочитал. Это просто природа конкретного примера, приведенного внутри. «Что происходит, когда вы определяете что-то, не давая определения? [...] Это просто помечается как «определенное» [...]?» -- НЕТ. Он определяется как макрос с указанным списком замены, который оказывается пустым.
@JohnBollinger Кажется, вы хотите обсудить тонкое различие между «пустым списком» и «нет значения». Технически вы правы, в лучшем случае правы, но вопрос не в этом. Теперь я понимаю, какой бит подразумевает, что определенность - единственный вариант использования, я смягчу это.
Не совсем. Меня не волнует, скажете ли вы «определено как ничего» или «определено как пустой список». Меня волнует, что вы неправильно говорите: «нет определения». В конечном счете, важным моментом является то, что определение макроса с пустым списком/без замены не является особым случаем, вопреки тому, что думает ФП.
@JohnBollinger Имейте в виду, что ОП использует C уже год.
@Schwern год - это количество времени, в течение которого я полностью свободно владею C, например. способен превратить (большинство) полностью реализованных идей проекта в код
Точный исходный код компилятора был бы замечательным.
#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, я подчеркиваю, что определение макроса, о котором спрашивает ОП, не является особым случаем. Видите жирный курсив? Он дает определение символа, какие бы слова вы ни использовали для этого, вопреки тому, что, по-видимому, предполагает ФП. Вся остальная семантика вытекает из этого. Что касается примеров использования, помимо проверки на определенность, я, конечно, мог бы их привести, но разве вы не настаивали в другом месте на строгом сосредоточении внимания на объеме вопроса?
ОП является новым для C, я сомневаюсь, что большая часть этого разговора имеет для них смысл. Вы эксперт, вы подчеркиваете это различие. Я предполагаю, что это важно в каком-то тонком смысле, но я этого не вижу. Для моего собственного образования не могли бы вы показать пример, где это различие важно?
Я бы не стал продавать короткий ОП, @Schwern, но, поскольку вы апеллируете к их предполагаемой неискушенности в отношении языка, я утверждаю, что «это не особый случай» имеет меньшую когнитивную нагрузку, чем «здесь есть специальные правила для этого». случай", а первый избегает замешательства, когда позже они обнаруживают случай, когда макрос, о котором они спрашивают, используется не только как флаг. Даже игнорируя второй из них, я просто не понимаю, почему вы предпочитаете описание особого случая. В любом случае, я привел пример, который, на мой взгляд, вполне доступен.
Спасибо за пример, и ваш ответ намного лучше. Думаю, я вижу различие, которое вы проводите: «EXTERN определяется как пустой список [чего?], который ничем не расширяется». Тогда как «EXTERN определяется как ничто и ни к чему не расширяется» подразумевает особый случай. Но для этого необходимо знать, что это список токенов (и что «ничего» и «пустой список» — это разные вещи), а это требует знания грамматики. Если вы это уже знаете, вы, вероятно, не зрители. «Ничто не превратится в ничто» — это естественное предположение. Это методика обучения, при которой сложность вводится постепенно, а не сразу.
@Schwern, не могли бы вы немного рассказать об этой сложности, пожалуйста? Приносим извинения за поздний ответ. Я выбрал C в качестве основного языка (хотя при необходимости могу использовать язык более высокого уровня, например Java), и после ознакомления с тем, как все работает, я хочу узнать больше о том, как это работает. Боюсь, мне не удалось найти конкретные детали достаточно хорошо, поэтому я создал этот пост, чтобы найти больше. Кстати, ваш ответ помог, спасибо за это.
@synnøve Например, можно сказать, что #define this x.y
означает замену this
на x.y
, а #define this
означает замену this
ничем. Это короткое, понятное и полезное объяснение. Технически правильное объяснение заключается в том, что #define this x.y
означает замену this
списком замен токенов препроцессора x
и .
и y
, тогда как #define this
означает замену this
пустым списком токенов препроцессора. Это сложнее понять, и возникают новые темы: что такое токен препроцессора? что значит заменить что-то пустым списком?
@synnøve Изучение C очень полезно, вы узнаете много нового о том, как на самом деле работают компьютеры. Однако, если вы только учитесь программировать, я бы посоветовал вам одновременно изучать язык более высокого уровня. C очень старый и, по современным меркам, довольно плохой язык (за это меня ждет ад), полный ловушек и подводных камней и требующий изучения многих вещей одновременно. Если вам нужен быстрый современный язык системного программирования, рассмотрите Rust или Go.
Имя макроса затем помечается как определенное, с пустым списком замены. Вас интересует исходный код компилятора? Более интересными для пользователей являются конкретные правила языка C, например en.cppreference.com/w/c/preprocessor/replace. «Как» — вопрос неопределенный.