Категория: Windows: Переводчики
Я являюсь адептом #pragma once. использую эту директиву во всех своих проектах и поощряю других к тому же. Тем не менее время от времени приходится натыкаться на код с использованием header guards. причём код относительно свежий, без налёта 20-го века. Сей факт меня, как представителя homo sapience (с акцентом на sapience) не перестаёт удивлять.
Я честно поднял для себя этот вопрос, попытался проанализировать, что же движет программистами обоих кланов. Список достоинств и недостатков обоих методов получился примерно следующим:
1. header guards - это хак и как любому хаку в коде ему не место. Более того, это хак в квадрате из-за использования макросов, что сами по себе есть зло. Логика подсказывает, что если же хак превращается в правило - проблему нужно решать на глобальном уровне, причём стандарт должен минимизировать побочные эффекты к нулю. Именно отсюда и родился #pragma once - тот самый стандарт, призванный решить существовавшие проблемы с включением заголовочников.
2. Конфликт имён. Если вы в своём коде добавили util.h с #define UTIL_H внутри, рано или поздно вы наткнётесь на конфликт с 3rdparty-библиотекой с таким же макросом. Единые правила для выбора имён макросов дискредитируют саму их идею - если все будут их придерживаться, то в результате они породят те же конфликты. Остаётся надеяться на генераторы мусорных суффиксов, которые а) идут вразрез с идеей того, что код должен писаться для человека и б) привет, рефакторинг! то-есть, пока. переименовав один заголовочник в другой или просто скопировав код получаем чёрти что.
3. Поощрение к хитростям. Если макрос объявлен, значит его можно и удалить, чтобы потом попробовать включить заголовочник снова. Или смешная до коликов в животе попытка «ускорить» компиляцию следующим хаком:
Которая не только ни на грамм не быстрее, но ещё и вынуждает следить по всему коду, чтобы макрос совпадал.
Конечно подкованный кодер к подобным хитростям чужд, максимум кого ими можно обхитрить - самого себя. Тем не менее неоднозначная семантика header guards вынуждает молодого программиста задуматься - а вдруг таки можно? Однозначная семантика #pragma once решает эту проблему на корню, минимизируя возможные хаки и ошибки к нулю.
4. Распространённость и поддержка компиляторами. Как известно, чем проще код - тем легче его сопровождать. Аргумент сложности реализации #pragma once для меня вообще смешон, поддержка реализуется примерно следующим алгоритмом:
Если кто сможет назвать хоть один компилятор (помимо мирно лежащих в гробах) без поддержки #pragma once - будьте добры. Мой гугл находит только кивания, что суслик как бы есть, но в глаза его никто не видел.
5. Миф о проблемах с символическими ссылками в #pragma once. Давно развеян с тех пор, как в GCC в кеш (см. пример выше) стали ложить не символические ссылки на файлы, а сами файлы.
6. Достоинства header guards. Здесь прочерк.
Итого я пришёл к выводу, что header guards продолжают пользоваться:
1) Осознанно. Бородатые программисты с бородатыми компиляторами со специфичным кодом под специфичные устройства.
2) Неосознанно - все остальные.
Отчасти на пункт 2 повлияли два фактора - ISO и GCC. Первый, насколько я понимаю, до сих пор жмётся, чтобы добавить директиву в стандарт. Второй долгое время выдавал warning на эту директиву, мол deprecated и вообще ай-яй-яй. Пользователи при этом балансировали на грани, а многие поддались общей панике и устроили крестовые походы набеги на #pragma once. насаждая всюду религию header guards. И видимо по инерции не заметили, как ещё в 2004-м году (!!) в GCC 3.4 официально убрали любые комплексы по этому поводу, оставив #pragma once навсегда в списке поддерживаемых директив.
Аргументы за и против, а также список компиляторов без поддержки #pragma once - приветствуются. Примеры сотен кода, в которых используются header guards без аргументации почему - можете оставить себе.
Правильный заголовок этой статьи — #pragma once. Он показан некорректно из-за технических ограничений.
В языках программирования Си и C++ #pragma once — нестандартная, но широко распространенная препроцессорная директива. разработанная для контроля за тем, чтобы конкретный исходный файл при компиляции подключался строго один раз. То есть, #pragma once применяется для тех же целей, что и include guard. но требует меньше кода и не допускает возможности коллизии имен.
File «child.c»
Достоинства и недостаткиПрименение #pragma once вместо include guard увеличит скорость компиляции во многих случаях благодаря высокоуровневому механизму; компилятор может самостоятельно сравнивать имена файлов или inode 'ы без необходимости вызова препроцессора Си для проверки заголовка на наличие #ifndef и #endif .
С другой стороны, некоторые компиляторы, как например, GCC. также использует специальный код для распознавания и оптимизации обработки include guard. [1]
Из-за того, что компилятор сам по себе отвечает за обработку #pragma once. программисту нет необходимости создавать новые имена, как например, GRANDFATHER_H в примере статьи об Include guard. Это исключает риск коллизии имён, то есть заголовочный файл не сможет вызвать ошибку при его подключении. Также приходится меньше набирать текст, нежели при использовании include guard .
Тем не менее, такая высокоуровневая обработка ускоряется в обоих случаях, но программисту приходится полагаться на корректную обработку компилятором #pragma once. Если компилятор совершает ошибку, например, не может распознать ситуацию, когда две символьные ссылки с различными именами указывают на один и тот же файл, то компиляция завершается с ошибкой. Компиляторы, содержащие ошибки, связанные с #pragma once. используются в LCC-Win32 версии 2004 года [2] [3] и GCC версии 1998 года. [1] GCC первоначально выдавал предупреждение об отключении #pragma once. если компилируемый код использовал её. Тем не менее, в релизе 3.4 GCC код обработки команды #pragma once был исправлен для корректной работы с символьными и жёсткими ссылками. Данная возможность была сочтена полезной и предупреждение было убрано. [4] [5]
Можно использовать обе команды, #pragma once и include guards. для написания переносимого кода, что также может принести выгоду от применения #pragma once при оптимизации (если компилятор её поддерживает):
Правильный заголовок этой статьи — #pragma once. Он показан некорректно из-за технических ограничений.
В языках программирования Си и C++ #pragma once — нестандартная, но широко распространенная препроцессорная директива. разработанная для контроля за тем, чтобы конкретный исходный файл при компиляции подключался строго один раз. То есть, #pragma once применяется для тех же целей, что и include guard. но требует меньше кода и не допускает возможности коллизии имён. В наборе компиляторов GCC до версии 3.4 считалась устаревшей и для применения не рекомендовалась. [1] Однако из-за широкого применения это решение было изменено. [2]
В статье об include guard приводится пример ситуации, в которой нужно использовать тот или иной метод. Выходом является использование include guard. приведенное там же; Вариантом использования #pragma once может быть:
File «father.h»
File «child.c»
Достоинства и недостатки ПравитьПрименение #pragma once вместо include guard увеличит скорость компиляции во многих случаях благодаря высокоуровневому механизму; компилятор может самостоятельно сравнивать имена файлов или inode 'ы без необходимости вызова препроцессора Си для проверки заголовка на наличие #ifndef и #endif .
С другой стороны, некоторые компиляторы, как например, GCC. также используют специальный код для распознавания и оптимизации обработки include guard. [1]
Из-за того, что компилятор сам по себе отвечает за обработку #pragma once. программисту нет необходимости создавать новые имена, как например, GRANDFATHER_H в примере статьи об Include guard. Это исключает риск коллизии имён, то есть заголовочный файл не сможет вызвать ошибку при его подключении. Также приходится меньше набирать текста, нежели при использовании include guard .
Тем не менее, такая высокоуровневая обработка ускоряется в обоих случаях, но программисту приходится полагаться на корректную обработку компилятором #pragma once. Если компилятор совершает ошибку, например, не может распознать ситуацию, когда две символьные ссылки с различными именами указывают на один и тот же файл, то компиляция завершается с ошибкой. Компиляторы, содержащие ошибки, связанные с #pragma once. используются в LCC-Win32 версии 2004 года [3] [4] и GCC версии 1998 года. [1] GCC первоначально выдавал предупреждение об отключении #pragma once. если компилируемый код использовал её. Тем не менее, в релизе 3.4 GCC код обработки команды #pragma once был исправлен для корректной работы с символьными и жёсткими ссылками. Данная возможность была сочтена полезной и предупреждение было убрано. [5] [6]
Можно использовать обе команды, #pragma once и include guards. для написания переносимого кода, что также может принести выгоду от применения #pragma once при оптимизации (если компилятор её поддерживает):
03.04.2014, 16:07
куча разных применений.
ИМХО, ей лучше вообще не пользоваться.
ИМХО, это не стандартная директива (в стандарте ее нет), поэтому используют ее разные компиляторы по-разному.
После слова прагма идет значение.
Например, прагмой можно изменить точку входа:
#pragma comment(linker,"/ENTERY:WinMain")
по умолчанию точка входа - это функция main(), но можно замутить другую, иногда в этом есть смысл.
В прагма коммент вообще очень много всего, там можно покопаться с екциями данных и кода, установить выравнивания (вроде как размер исполняемого файла при компиляции кратен какому-то числу, вот это число можно подредактировать), да много всего. Но я не уверен, я не особо этим баловался.
pragma pack - вот этой штукой я пользовался, и даже студентов ей пытал. Есть такая штука в С++, как выравнивание полей в структурах.
грубо говоря
sizeof(T); // какое значение вернет sizeof? -
студенты могут считать что 1, ну и по хреновым методичкам оно там и есть, но а реально все зависит от того, как выравнивает поля компилятор, а управлять этим можно с pragma pack
Еще часто пользуют pragma once чтобы модуль включался при линковке только один раз. Но вместо pragma once всегда можно использовать #ifndef и #define. Вобщем в основном так и делают.
Без лишней необходимости ими лучше не пользоваться, потому что даже прагма пак, которая известна и реально нужна работает не во всех компиляторах.
Вот такая штука эта прагма, я то стараюсь писать портируемый код, поэтому ими не балуюсь. Ну мало ли, напишу я софтину и захочу ее на ведре запустить, но где гарантии, что компилятор С++ на ведре сработает также как компилятор мелкософта или гцц? - никто таких гарантий не дает (и никто не обязан это обеспечивать).
В стандарте я ее чесно не искал, поэтому ИМХО всюду написал.
Директива #pragma определяется стандартом ANSI С для реализации директив, предоставляющих компилятору различные инструкции. Стандартный вид #pragma следующий:
Директива argused должна стоять перед функцией. Она используется для устранения предупреждений, если аргумент функции, перед которой стоит #pragma, не используется в теле функции.
Чтобы разрешить использовать анонимные структуры, укажите директиву anon_struct. С помощью директивы codeseg можно указать сегмент, класс или группу, используемую функцией.
Используя директиву comment, в выходной файл, например, в файл с расширением obj или ехе можно поместить комментарии.
Директива exit определяет одну или несколько функций, вызываемых при завершении программы. Директива startup определяет одну или несколько функций, вызываемых при запуске программы. Они имеют следующий вид:
#pragma exit имя_функции приоритет
#pragma startup имя_функции приоритет
Приоритет — это значение от 64 до 255 (значения от 0 до 63 зарезервированы). Приоритет определяет порядок вызова функций. Если приоритет не указан, то по умолчанию предполагается значение 100. Все функции, выполняющиеся в начале или конце программы, должны объявляться следующим образом:
void f(void);
Следующий пример определяет функцию start(), выполняющуюся в начале программы.
#include <stdio.h>
void start(void);
#pragma startup start 65
In the C and C++ programming languages, #pragma once is a non-standard but widely supported preprocessor directive designed to cause the current source file to be included only once in a single compilation. Thus, #pragma once serves the same purpose as #include guards. but with several advantages, including: less code, avoiding name clashes, and improved compile speed.
File "child.c"
Advantages and disadvantagesUsing #pragma once instead of include guards will typically increase compilation speed since it is a higher-level mechanism; the compiler itself can compare filenames or inodes without having to invoke the C preprocessor to scan the header for #ifndef and #endif.
Some compilers such as GCC include special speedup code to recognize and optimize the handling of include guards, and thus little or no speedup benefit is obtained from the use of #pragma once. [1]
Again because the compiler itself is responsible for handling #pragma once. it is not necessary for the programmer to create new macro names such as GRANDFATHER_H in the Include guard article's example. This eliminates the risk of name clashes, meaning that no header file can fail to be included at least once. It also requires less typing than the include guard method.
However, this high-level handling cuts both ways; the programmer must rely on the compiler to handle #pragma once correctly. If the compiler makes a mistake, for example by failing to recognize that two symbolic links with different names point to the same file, then the compilation will fail. Compilers with #pragma once -related bugs included LCC-Win32 as of 2004 [update] [2] [3] and GCC as of 1998 [update]. [4] GCC originally gave a warning declaring #pragma once "obsolete" when compiling code that used it. However, with the 3.4 release of GCC, the #pragma once handling code was fixed to behave correctly with symbolic and hard links. The feature was "un-deprecated" and the warning removed. [5] [6]
You can use both #pragma once and include guards to write portable code that can also take advantage of #pragma once optimizations the compiler may support:
Include guards always seemed like an inconvenient way of making sure header files are only included once to me. Surely it would be better to have a special preprocessor directive to indicate the less common case where you really do want to allow your header to be included multiple times?
Whatever the case, #pragma once has existed for a very long time as an alternative to include guards. Why would you want to use it?
For whatever reason, use of #pragma once is not especially widespread. There’s usually no compelling reason not to use it though. Arguments against its use are usually:
There are two example situations where you could end up breaking pragma once that I can think of, and neither would form the basis of a particularly compelling argument against using it:
The first example is not an argument against pragma once at all. Having multiple copies of the same header is poor from a maintenance point of view and will be less efficient to build than if the header exists in only one location. If multiple copies need to be made for some reason (e.g. some sort of build system?) they should be made as links.
скомпилируем main.c и запустим
> cc main.c && ./a.out
Вопрос, чему будет равно x?
Ок, а теперь внимательно следим за руками:
> touch -r a.h b.h # выравниваем mtime у a.h и b.h
> touch a.h
> cc main.c -MM
main.o: main.c a.h b.h
Обычные зависимости, ничего магического. А теперь снова проделаем наш фокус:
> touch -r a.h b.h
> cc main.c -MM
main.o: main.c a.h
Круто! b.h нам больше не нужен!
Если пораскинуть мозгами, то можно догадаться, что содержимое хедеров у нас одинаковое, mtime у них одинаковый, то есть, думает gcc, это один и тот же файл! Ну а раз такое дело, да ещё и #pragma once, то второй раз тот же хедер инклюдить не нужно.
Действительно, если заглянуть в код gcc (libcpp/files.c:815), то можно увидеть, что, в случае #pragma once (once_only), если mtime и size инклюда совпадает с ранее включённым файлом, то после сравнения содержимого, если файлы идентичны, то функция возвращает false и файл не инклюдится.
/* We may have read the file under a different name. Look
for likely candidates and compare file contents to be sure. */
for (f = pfile->all_files; f; f = f->next_file)
if (f == file)
if ((import || f->once_only)
&& f->err_no == 0
// дальше здесь файл f зачитывается ещё раз в память и сверяется с текущим
// и если у них одинаковое содержимое, то функция возвращает false и не инклюдит файл
same_file_p = read_file (pfile, ref_file)
/* Size might have changed in read_file(). */
&& ref_file->st.st_size == file->st.st_size
&& !memcmp (ref_file->buffer,
file->buffer,
file->st.st_size);
Я то всегда думал, что #pragma once срабатывает при инклюде того же файла в смысле "по тому же пути", ан нет.
В ченджлоге от версии 3.4 можно увидеть небольшую заметку про #pragma once