Evg |
СОДЕРЖАНИЕ
Препроцессорные директивы в C/C++ (#include, #define и прочее)
Запись от Evg размещена 15.02.2012 в 23:01
Показов 506359
Комментарии 12
|
ВНИМАНИЕ! Вопросы по существу обсуждаемого вопроса просьба задавать здесь или создать тему на форуме и кинуть на неё ссылку в блог или мне в личку.
Объясняю почему
Причин для этого несколько. Я, как и любой другой автор, всегда могу упустить интересный момент обсуждаемой темы (что подтвердилось на практике). А потому задаваемый вопрос может закрывать пробел в статье. Ответ на конкретный вопрос, как правило, дать несложно. Сложнее его аккуратно сформулировать так, чтобы ответ являлся законченной частью статьи. Поэтому, как правило, на первых порах я ограничиваюсь конкретным ответом на конкретный вопрос, а в статью временно вставляю ссылку на пост, где был дан ответ. А когда дойдут руки, то вместо ссылки пишу нормальное пояснение. Технические возможности блога не позволяют в комментариях пользоваться широкими возможностями, доступными на форуме (то как выделение текста жирным, вставка фрагментов исходников в удобном для чтения виде и т.п.), поэтому будет удобнее, если вопрос и ответ будут опубликованы на форуме Любая статья является изложением знаний в общем случае. У многих людей мышление устроено так, что прочтя на форуме конкретный вопрос и конкретный ответ на этот вопрос, у них появится бОльшее понимание, чем после прочтения теоретических выкладок (даже если они подкреплены конкретными примерами). Ссылки на такие обсуждения я, как правило, включаю в последний раздел статьи. Начинающие, как правило, поиск ответов на свои вопросы ведут именно в форуме, а не в блогах. А потому конкретный вопрос и конкретный ответ для них будет более удобным и полезным именно на форуме. Многие люди умеют работать методом тыка, лишь бы был конкретный пример в качестве образца. А потому такое обсуждение будет им полезным даже без прочтения статьи Исторически сложилось, что раньше (когда ещё не было блога) статьи располагались на форуме и представлены были в виде двух тем. Первая тема создавалась в специально отведённой свалке и представляла собой черновик, который со временем дорабатывался до законченной статьи. После этого статья переезжала во вторую тему в тематическом разделе. А первая тема оставалась дополнительной свалкой для замечаний и мелких вопросов по теме. Ссылку на старое местоположение данной свалки я помещаю в начале статьи. Вопросы, по возможности, прошу создавать в отдельных темах, но если вопрос действительно мелкий, то можно его задать и в указанной свалке.
1. Что такое препроцессор 1.1. Общие сведения Во всех компиляторах с языков C/C++ есть некая фаза, называемая препроцессированием. Фаза эта запускается автоматически и по большому счёту является прозрачной для пользователя (программиста). Т.е. пользователь в большинстве случаев препроцессор самостоятельно НЕ запускает. Препроцессирование - это процесс, на вход которого подаётся текст (текстовый файл) и на выходе формируется текст. Во время работы препроцессор занимается тем, что видоизменяет исходный текстовый файл. И только после этого изменённый текстовый файл в дальнейшем попадает в компиляцию. Команды препроцессора (их называют директивами) начинаются на символ #, который должен первым непробельным символом в строке. Первыми директивами препроцессора, с которыми сталкиваются начинающие, являются директивы #include и #define Замечу также, что на этапе препроцессирования удаляются из текста все комментарии 1.2. Как посмотреть результат работы препроцессирования Все вменяемые компиляторы предоставляют возможность для того, чтобы посмотреть результат работы препроцессирования. Поэтому начинающим будет полезно узнать о том, как это делается - так проще будет проводить эксперименты 1.2.1 Компилятор gcc (он же mingw под windows) В компиляторе gcc есть опция -E, которая печатает в терминале результат работы препроцессора и завершает компиляцию - т.е. код формироваться не будет. Если выдача оказывается слишком большой, то её можно перенаправить в файл через ">" в командной строке, либо подать опцию "-o <file>" в запуск gcc C /* Файл a.c */ int x = 10; Code $ gcc a.c -E # 1 "a.c" # 1 "<built-in>" # 1 "<command-line>" # 1 "a.c" int x = 10; Посмотреть список файлов, подключенных через директиву #include (см. раздел 2), можно по опции -H. В этом случае компиляция НЕ останавливается C /* Файл a.c */ #include <stdio.h> Code $ gcc a.c -H . /usr/include/stdio.h .. /usr/include/features.h ... /usr/include/sys/cdefs.h .... /usr/include/bits/wordsize.h ... /usr/include/gnu/stubs.h .... /usr/include/bits/wordsize.h .... /usr/include/gnu/stubs-32.h .. /usr/lib/gcc/i486-linux-gnu/4.2.4/include/stddef.h .. /usr/include/bits/types.h ... /usr/include/bits/wordsize.h ... /usr/include/bits/typesizes.h .. /usr/include/libio.h ... /usr/include/_G_config.h .... /usr/lib/gcc/i486-linux-gnu/4.2.4/include/stddef.h .... /usr/include/wchar.h ... /usr/lib/gcc/i486-linux-gnu/4.2.4/include/stdarg.h .. /usr/include/bits/stdio_lim.h .. /usr/include/bits/sys_errlist.h Multiple include guards may be useful for: /usr/include/bits/stdio_lim.h /usr/lib/gcc/i486-linux-gnu/4.2.4/../../../../lib/crt1.o: In function `_start': (.text+0x18): undefined reference to `main' collect2: ld returned 1 exit status См. тут и тут. FIXME написать по человечески 1.2.3 Borland Builder В C++ Builder 2007 на файле в списке исходников можно нажать правую кнопку и далее выбрать "Preprocess". Потарахтев несколько секунд, в списке исходников родится ещё один файл с расширением .i - это и есть препроцессированный текст FIXME написать по человечески Люди, кто знает, есть ли возможность посмотреть препроцессированный текст непосредственно из Qt-Creator'а (не запуская ручками g++ в командной строке) 2. Директива #include 2.1. Общие сведения Директива #include ничего умного не делает, она просто целиком подставляет файл, который передан параметром директиве. Допустим, мы имеем следующие исходники: C /* Файл t1.c */ #include "t.h" int main (void) { struct str s; s.x = 1; s.y = 2; return func (&s); } C /* Файл t2.c */ #include "t.h" int func (struct str *p) { return p->x + p->y; } C /* Файл t.h */ /* Комментарий к нашей структуре */ struct str { int x, y; }; extern int func (struct str *p); C <-- комментарии удаляются на этапе препроцессирования (здесь было "/* Файл t1.c */") <-- В этом месте находилась директива "#include", с этой точки начинается подстановка файла t.h struct str { int x, y; }; extern int func (struct str *p); <-- Закончили подстановку t.h, продолжаем подстановку файла t1.c int main (void) { struct str s; s.x = 1; s.y = 2; return func (&s); } C struct str { int x, y; }; extern int func (struct str *p); int func (struct str *p) { return p->x + p->y; } Структуру "struct str" мы описывали только один раз, но её описание попало в оба файла (t1.c и t2.c), причём описание одно и то же. Без файла t.h нам бы пришлось структуру описывать два раза, причём при каждом изменении следить, чтобы эти изменения в обоих файлах были одинаковые. А файлов могло быть на два, а сто или тысяча. Описание функции func так же попало в оба файла, что даёт дополнительную проверку со стороны компилятора, если мы поменяем прототип функции func в файле t2.c, но при этом не поменяем в файле t.h, то компилятор схватит нас за руку и выдаст сообщение об ошибке. Если бы мы не пользовались файлом t.h, то могло быть так, что в файле t1.c внешняя функция func описана с одним прототипом, а в файле t2.c реализована с другим прототипом. Компилятор бы в этом месте ошибку не выдал, т.к. каждый файл *.c компилируется по отдельности То, что подключаемый файл имеет расширение *.h - это обычная условность. Через директиву #include можно подключать абсолютно любой текстовый файл 2.2. Различия между угловыми скобками и кавычками Есть некие соглашения, что имена системных файлов пишутся в угловых скобках, а пользовательских - в кавычках. Сильно принципиальных отличий между системными и пользовательскими файлами нет, и отличия эти варьируют в зависимости от компилятора. Обычно компилятор не выдаёт warning'и в местах, возникших в системных файлах (из тех соображений, что разработчик компилятора может накосячить, и у пользователя не будет возможности обойти эти warning'и). Ещё одно отличие файла в кавычках от файла в угловых скобках может заключаться в том, что файл в кавычках ищется сначала в текущем каталоге (а точнее в каталоге, где лежит компилируемый файл), а потом в прочих каталогах, а вот файл в угловых скобках ищется только в прочих каталогах - так, например, поступает компилятор от MSVS 2.3. Где компилятор ищет файлы для подключения через #inlcude У компилятора, как и у большинства системных программ, есть некие заданные пути поиска файлов. Когда компилятор запускает препроцессор, то через опции он подаёт пути поиска для файлов *.h. Препроцессор, встретив директиву #include сначала ищет файл в текущем каталоге (это может быть пропущено для файла в угловых скобках - см. главу 10.1), и если не находит, то ищет по тем путям, которые переданы ему компилятором. На разных платформах файлы с include'ами находятся в разных местах. На unix'ах это как правило каталог /usr/include, на виндузовых компиляторах includ'ы обычно находятся внутри каталога, куда установлен компилятор 3. Директивы #define и #undef 3.1. Директива #define Директива #define определяет так называемые макросы. Грубо говоря, если мы напишем "#define TRAM 10", то в процессе работы препроцессора все вхождения буквосочетания "TRAM" будут в текстовом виде заменены на "10". При этом надо отметить, что "TRAM" должно быть отдельным токеном (т.е. вокруг должны быть либо пробельные символы, либо знаки препинания). Т.е. "TRAM+TRAM" будет заменено на "10+10", а вот "TRAMPAMPAM" останется без изменений Для следующего примера
Что в итоге мы имеем?. В конкретно данном примере этим макросом N я задал размер массива, а потом везде работал именно через макрос. Можно сразу написать 20, с точки зрения конкретно данного примера ничего не поменяется. НО. Если мне нужно поменять размер массива с 20 на 30, то я просто меняю значение define'а, а во всех остальных местах это фактически изменится автоматически. Если же писать непосредственно 20, то потом во всех местах надо менять 20 на 30, а таких мест может быть много Работать с define'ами надо аккуратно. Если написать такой код:
3.2. Директива #undef Для того, чтобы отменить макрос, существует директива #undef. Как только препроцессор встречает такую директиву, он "забывает" опеределённый ранее макрос и больше не заменяет его. Для демонстрации этого свойства опять вернёмся к предыдущему примеру и добавим туда макрос #undef
3.3.1. Общие сведения Определение макроса, при котором одно буквосочетание заменяется на другое, является простейшей макроподстановкой. Однако препроцессор понимает и более сложные макросы - макросы, содержащие параметры. Смысл таких макросов примерно такой же, как и для функций в языка программирования - выделить в одно место часто повторяющиеся действия и задать эти действия параметрами. При этом надо не забывать и чётко себе представлять, что макрос - это не функция, а текстовая замена Пояснить проще всего на примере. Зададим макрос, в результате подстановки которого мы будем получать квадрат значения параметра, подаваемого в макрос
При написании макроса с параметрами одной из распространённых ошибок начинающих, является наличие пробела между именем макроса и открывающей скобкой при описании директивы #define. Это неправильно, открывающая скобка должна идти впритык к имени макроса
При этом критичным является лишь то, что между именем макроса и открывающей скобкой не должно быть пробелов. Во всех остальных местах пробелы можно ставить в произвольном количестве 3.3.3. Макрос - это совсем не функция Ещё одна распространённая ошибка демонстрируется следующим примером
Однако даже такой вариант не сможет отработать корректно в 100% случаев. Макрос, в теле которого параметр используется более одного раза в общем случае работает некорректно (в том смысле, что отработает не так, как от него ожидали). Если в качестве параметра подать конструкцию, значение которой меняется при каждом обращении, то получим некорректный код. Например, глядя на текст
3.3.4. Оператор # для параметров макроса Препроцессор работает с файлом строго как с текстом. То же самое касается и параметров директивы #define. Следовательно, препроцессору ничего не стоит делать с ними некоторые простые преобразования. Одним из них является символ '#'. Если его поставить перед параметром макроса, то в результате подстановки этот параметр будет взят в кавычки
3.3.5. Оператор ## в теле макроса Ещё одним оператором для параметров макроса является ##. Если его поставить между двумя "словами" в теле макроса, то эти два "слова" будут склеены в одно (так называемый оператор конкатенации):
3.3.6. Директива #define с переменным числом параметров FIXME Написать, а пока ссылка на пример использования enum В качестве примера использовать синтетический тест. Создаём макрос, который вычисляет сумму своих аргументов. Типа SUM(2,x,y), SUM(5,x,y,z,a,b). В качестве реального примера сослаться на реализацию syscall'ов в linux'овом unustd.h 3.3.7. Круглые скобки или запятая среди параметров макроса FIXME Написать, а пока ссылка на пример использования Использование строк в макросах С++ Как добавить запятую в аргументы макроса? 3.4. Директива #define, растянутая на несколько строк При написании макроса, его тело может оказаться довольно длинным. В этом случае для удобства можно его разбивать на несколько строк, но при этом в конце каждой строки, кроме самой последней, надо будет ставить символ обратного слэша, который по сути дела говорит препроцессору, что последующий символ перевода строки надо будет проигнорировать и считать, что вся следующая строка как бы является продолжением текущей
3.5. Использование в директиве #define имён других макросов Пока написано тут и тут FIXME перенести сюда и аккуратно переписать (хотя вроде бы там написал всё как надо) Ещё один интересный момент тут FIXME перенести сюда и аккуратно переписать 4. Директивы условной компиляции 4.1. Общие сведения об условной компиляции В любой программе есть условные ветки исполнения кода. Точно так же препроцессор позволяет условно включать те или иные фрагменты исходника программы. Для этого используется препроцессорная директива "#if <условное выражение>". Прежде, чем делать подробные объяснения, попробую сначала показать на простом примере, как это использовать. Допустим мы пишем программу, которая содержит отладочные печати. Эти печати имеет смысл включать только тогда, когда разработчик программы занимается её отладкой. Версия, которую программист отдаёт пользователю, этих печатей не должна содержать. Для этого, например, можно в программе завести переменную и ставить все печати под условие:
В качестве примера я привёл отладочную печать, но вместо отладочной печати может вообще стоять какая-то функциональность. Многие программные продукты распространяются в том виде, что полная версия делается платной, а свободных (бесплатных) версиях отключается часть функциональности. Если это делать приведёным выше способом, то можно попросту покопаться в бинарнике и на месте переменной debug воткнуть единицу, после чего урезанная программа превращается в полную (условно говоря). Чтобы избежать этих проблем, ненужный код надо физически вырезать из программы. И для этих целей удобно использовать директиву условной компиляции "#if"
Немного забегая вперёд, скажу, что конкретно это место я бы переписал чуть-чуть по другому. Об этом более подробно расскажу в разделе 8, т.к. здесь описываю только принцип условной компиляции Аналогично следует поступать, когда из программы нужно вырезать некоторую функциональность, только получится здесь чуточку сложнее. Допустим, у нас есть программа, которую следует собирать в трёх конфигурациях: lite, medium, full. Схематично выглядеть будет примерно так:
Синтаксис директивы "#if" очень простой и по большому счёту совпадает с операциями условного исполнения в языке. Как-то особенно на этом останавливаться не буду, просто покажу все варианты использования. Единственное, что нужно отметить - обязательно должен быть "#endif", потому как без него препроцессор не сможет понять, в каком месте заканчивается директива "#if"
Если обратиться к примерам из предыдущего раздела и скомбинировать их, то можно продемонстрировать один из примеров использования. Ситуация следующая: в программе была найдена ошибка, но аккуратное её исправление требует много времени, а исправить надо быстро. Поэтому пока делаем в виде затычки.
Жизнь показала, что не всем понятно, что такое "затычка" по своей сути. Здесь приведён хороший пример с пояснением. Пример основан на реальной ошибке в коде 4.3. Директивы #ifdef, #ifndef В примере из предыдущего раздела мы ввели макрос BUG1234, у которого не было никакого значения. Такое используют в случаях, когда некоторый настроечный макрос может принимать только два значения (условно 0 и 1). В этих случаях вместо значений 0 и 1 зачастую используют макрос без значения, а проверку делают на основании того, установлен макрос или нет - т.е. через "if defined ...". Для таких случаев были придуманы сокращённые варианты "#ifdef <macro_name>" и "ifndef <macro_name>". Параметрами этих директив является не логическое выражение, а один-единственный макрос. Конструкция "#ifdef BUG1234" эквивалентна конструкции "#if defined BUG1234", "#ifndef BUG1234" эквивалентно "#if (! defined BUG1234)". Принципиально новых возможностей эти две конструкции не вносят, просто сокращённая форма записи и не более того. Каким принципом пользоваться (т.е. "иметь макрос со значениями 0 и 1" или "иметь или не иметь макрос") - это чисто дело вкуса. Я, как правило, пользуюсь философскими соображениями на тему "включено/выключено" или "присутствует/отсутствует". В случае макроса DEBUG я считаю, что отладочный код он всегда есть, только где-то он включен, а где-то выключен, а потому использую макрос со значением. В случае BUG1234 я считаю затычку временным явлением, а потому она либо присутствует, либо отсутствует, поэтому использую макрос без значения и проверки на то, взведён макрос или нет. 5. Директивы #error и #warning 5.1. Общие сведения Основная область применений данных директив - исходники программ, предназначенные для широкого пользования: open source проекты или программы, которые пишут одновременно большое количество людей. Директивы не делают ничего умного, кроме как заставляют препроцессор (или компилятор, возможно, зависит от конкретной реализации) выдать сообщение с ошибкой или предупреждением. Директивы применяют совместно с директивами условной компиляции, чтобы отсечь некоторые недопустимые или неподдерживаемые комбинации настроек. 5.2. Директива #error В качестве примера можно взять следующее. Допустим мы пишем программу, которая может работать на разных платформах. При этом в момент компиляции нужно знать, наша платформа big endian или little endian. На разных платформах работают компиляторы от разных разработчиков. Как правило, каждый компилятор выставляет некие предопределённые макросы, в том числе и макросы, определяющие тип процессора. Например, большинство компиляторов под intel'овскую архитектуру выставляют макрос __i386__, компиляторы под sparc'овскую архитектуру выставляют макрос __sparc__ и т.д. Но каких-то более-менее единых макросов, относящихся к endian'у архитектуры нет. Поэтому наиболее надёжным способом будет самим взвести какой-то макрос в зависимости от архитектуры. Можно это сделать следующим образом:
Смысл директивы #error именно в том, чтобы обнаруживать такие неподдерживаемые или недопустимые комбинации настроечных макросов и уже на этапе компиляции программы выдавать ошибку. 5.3. Директива #warning Директива "#warning" работает аналогичным образом, но выдаёт не ошибку, а предупреждение. Как правило делается это в тех случаях, когда хотят что-то поменять, но не сразу, а плавно: в какой-то период времени будут работать "старый" и "новый" варианты, но со временем старый вариант будет удалён. Например, мы имеем в программе макрос DEBUG, который либо включен, либо выключен. В какой-то момент мы решили, что количество отладочных печатей стало слишком большим и в них уже сложно разобраться. Вместо макроса DEBUG мы решили завести макрос DEBUG_LEVEL с цифровым значением от 0 до 10. Значение 0 означает отсутствие каких-либо печатей (и эквивалентно отсутствию макроса DEBUG в нынешней реализации), значение 1 означает самое минимально необходимое количество печатей, значение 2 - уже побольше печатей, значение 10 соответствует полному количеству печатей (и эквивалентно наличию макроса DEBUG в нынешней реализации). Если программу пишет сто человек, то такой резкий переход с ходу сделать не получится. Поэтому нужен какой-то временный период, в течение которого все люди смогут постепенно перейти от использования макроса DEBUG к макросу DEBUG_LEVEL. Проще всего в точке, где в нынешней реализации определяется макрос DEBUG, поступить следующим образом:
6. Директива #line FIXME Написать 7. Директивы, но не относящиеся к препроцессору (#pragma, #import и т.п.) FIXME Написать 8. Примеры использования препроцессорных директив на практике FIXME Написать
9. Когда следует использовать макросы, а когда конструкции языка программирования FIXME Написать
10. Ссылки на темы, где обсуждались подобные вопросы По данным ссылкам нет ничего, что выходило бы за рамки статьи. По этим ссылкам просто обсуждались какие-то отдельно взятые аспекты препроцессирования. Прочитать эти темы будет проще, чем читать большую статью
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 12
Комментарии
-
Запись от Avazart размещена 13.03.2013 в 18:00
-
Запись от Evg размещена 13.03.2013 в 21:49
-
Здравствуйте! Вы мельком упомянули про SUM с переменным числом аргументов. Я попытался реализовать его — у меня не получилось. Проблема в том, что рекурсивные макросы запрещены. Можно ли как-нибудь сделать SUM для произвольного количества аргументов?Запись от mymedia размещена 11.06.2015 в 00:22
-
mymedia, такое прокатит в GCC с сообщением, на счёт всех остальных не знаю (в Intel C++ Compiler не прокатывает)
GCC в режиме педанта говорит об этом так: "warning: ISO C++ forbids braced-groups within expressions [-Wpedantic]"C++ 1 2 3 4 5 6 7 8 9 10 11 12 13
static int my_max( size_t cnt, int values[] ) { int m = values[0]; while ( cnt-- ) { if ( values[cnt] > m ) m = values[cnt]; } return m; } #define MY_MAX( ... ) ({ \ int _args[] = { __VA_ARGS__ }; \ my_max( sizeof _args / sizeof *_args, _args ); \ })
Запись от castaway размещена 29.06.2015 в 22:20
-
mymedia, такое прокатит в GCC, на счёт всех остальных не знаю (в Intel C++ Compiler не прокатывает)
GCC в режиме педанта говорит об этом так: "warning: ISO C++ forbids braced-groups within expressions [-Wpedantic]"C++ 1 2 3 4 5 6 7 8 9 10 11 12 13
static int my_max( size_t cnt, int values[] ) { int m = values[0]; while ( cnt-- ) { if ( values[cnt] > m ) m = values[cnt]; } return m; } #define MY_MAX( ... ) ({ \ int _args[] = { __VA_ARGS__ }; \ my_max( sizeof _args / sizeof *_args, _args ); \ })
Запись от castaway размещена 29.06.2015 в 22:20
-
mymedia, я имел в виду примерно следующее:
Это стандартный приём. Что-то большего сделать на макросах нельзя. Аналогичный приём используется тут: https://www.cyberforum.ru/cpp/thread900778.htmlC#define SUM2(a1, a2) a1 + a2 #define SUM3(a1, a2, a3) a1 + a2 + a3 #define SUM4(a1, a2, a3, a4) a1 + a2 + a3 + a4 ... #define SUM(N, ...) SUM##N(__VA_ARGS__) int a, b, c, d, e, f; a = SUM (2, c, d); b = SUM (3, c, d, e); c = SUM (4, c, d, e, f);
Запись от Evg размещена 30.06.2015 в 12:05
-
А что обозначаетC++ 1
#define имя_идентификатора последовательность_символов //про это много информации
но без последовательность_символов ?C++ 1
#define имя_идентификатораЗапись от Инженер_3 размещена 21.06.2017 в 17:31
-
Емнип, тогда ему присваивается 0 по умолчанию. А в общем это используется тогда, когда значение константы неважно, важен сам факт ее определенности/неопределенности (чтобы потом проверять через defined, ifdef или ifndef). Например, в начале заголовочных файлов обычно определяют константу с именем, совпадающим с названием файла, и берут весь код в #ifndef с проверкой этой же константы - чтобы он не компилировался повторно в случае чего.
(Но я C++ последний раз работала сто лет назад, так что заранее прошу прощения, если фигню написала.)Запись от ilana размещена 22.06.2017 в 19:21
-
Preprocessor tricks: https://github.com/pfultz2/Clo... and-idiomsЗапись от Evg размещена 22.08.2017 в 17:26
-
Дополнительные замечания на предмет многострочного макроса: https://www.cyberforum.ru/faq/... st12932063Запись от Evg размещена 29.09.2018 в 14:21
-
Для тех, кто в танке, не понял https://www.cyberforum.ru/faq/... ost1911940 (я таким был,/* что из людей делает VS */
). Запускаем Command Prompt for VS <N> (ее название на русском "командная строка разработчика для VS <N>). Теперь вам доступен компилятор cl
. Далее перемещаемся в нашу папку: cd <путь папки>(ну или можно далее вызвать файл через путь). И вызываем препроцессор(читать пост по ссылке). Пример: . P.s. не пугайтесь бегущей водичкиC++ 1
cl /EP COMMN.cpp
.Запись от Dimgo2 размещена 13.07.2019 в 19:16
-
В теме https://www.cyberforum.ru/cpp-... 08087.html см. полезные описания от TheCalligrapherЗапись от Evg размещена 06.10.2019 в 12:01

). Запускаем Command Prompt for VS <N> (ее название на русском "командная строка разработчика для VS <N>). Теперь вам доступен компилятор cl
. Далее перемещаемся в нашу папку: cd <путь папки>(ну или можно далее вызвать файл через путь). И вызываем препроцессор(читать пост по ссылке). Пример:
.
