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





использование INT (3) для установки точки останова в коде - мой фаворит на все времена
Я не думаю, что это портативный. Он будет работать на x86, но как насчет других платформ?
Понятия не имею - вы должны задать вопрос по этому поводу
Это хороший метод, и он специфичен для X86 (хотя, вероятно, есть аналогичные методы на других платформах). Однако это не функция C. Это зависит от нестандартных расширений C или вызовов библиотеки.
В GCC есть __builtin_trap и для MSVC __debugbreak, которые будут работать на любой поддерживаемой архитектуре.
Что ж ... Я думаю, что одной из сильных сторон языка C является его переносимость и стандартность, поэтому всякий раз, когда я нахожу какой-нибудь «скрытый трюк» в реализации, которую использую сейчас, я стараюсь не использовать его, потому что стараюсь сохранить свои Код C как можно более стандартный и переносимый.
Но на самом деле, как часто вам нужно компилировать свой код с помощью другого компилятора?
@Joe D, если это кроссплатформенный проект, такой как Windows / OSX / Linux, возможно, немного, а также есть разные арки, такие как x86 vs x86_64 и т. д.
@JoeD Если только вы не участвуете в очень ограниченном проекте, который счастлив выйти замуж за одного поставщика компилятора, очень. Возможно, вы захотите избежать фактической смены компиляторов, но вы хотите, чтобы эта опция оставалась открытой. Однако со встроенными системами у вас не всегда есть выбор. АГС, АСС.
Чередующиеся структуры, такие как Устройство Даффа:
strncpy(to, from, count)
char *to, *from;
int count;
{
int n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
}
Я этого не понимаю. Я добавил ссылку, она отображается в превью, но не в финальном посте ...
Это неправильная функция strcpy - эта реализация предполагает, что «to» - это регистр с отображением в память.
@ComSubVie: вы создали ссылку, но еще не создали ее. К ответу нужно добавить что-то вроде этого: «[статья в Википедии] [1] на устройстве Даффа». Лично мне не нравится нотация Markdown для ссылок, я обычно использую теги привязки HTML напрямую.
@ComSubVie, любой, кто использует Устройство Даффа, является скрипачом, который видел Устройство Даффа и думал, что их код будет выглядеть 1337, если они будут использовать Устройство Даффа. (1.) Устройство Даффа не предлагает никакого увеличения производительности на современных процессорах, потому что современные процессоры имеют нулевые накладные расходы зацикливания. Другими словами, это устаревший фрагмент кода. (2.) Даже если ваш процессор не поддерживает цикл с нулевыми накладными расходами, он, вероятно, будет иметь что-то вроде SSE / altivec / vector-processing, что заставит ваше Устройство Даффа посрамить при использовании memcpy (). (3.) Я упоминал еще о том, что выполнение memcpy () duff's бесполезно?
@ComSubVie, познакомься с моим кулаком смерти (en.wikipedia.org/wiki/…)
Это ... УЖАСНО. зачем кому-то делать это с плохим беззащитным кодом?
@Trevor: значит, только скриптовые дети программируют 8051 и микроконтроллеры PIC, верно?
@Trevor Boyd Smith: Хотя устройство Даффа кажется устаревшим, это все еще историческое любопытство, которое подтверждает ответ ComSubVie. Во всяком случае, цитирую Википедию: «Когда многочисленные экземпляры устройства Даффа были удалены с сервера XFree86 в версии 4.0, производительность значительно улучшилась». ...
В Symbian мы однажды оценили различные циклы для быстрого кодирования пикселей; устройство даффа на ассемблере было самым быстрым. Так что сегодня он по-прежнему актуален для основных ядер ARM на ваших смартфонах.
@Trevor: Хотя устройство Даффа может быть устаревшим (это не так), его концепция (разворачивание цикла) - нет.
Ранние версии gcc пытались запустить игру всякий раз, когда встречали "#pragma" в исходном коде. См. Также здесь.
#pragma Идентификаторы яда GCC Эта директива запрещает использование идентификаторов в программе. Отравленные идентификаторы не могут быть # ifdef'd или # undef'd, и попытка использовать их для чего-либо приведет к ошибке. Идентификаторы - это список, разделенный пробелами.
C имеет стандарт, но не все компиляторы C полностью совместимы (я еще не видел полностью совместимого компилятора C99!).
Тем не менее, я предпочитаю уловки, которые неочевидны и переносимы между платформами, поскольку они полагаются на семантику C. Обычно они касаются макросов или битовой арифметики.
Например: замена двух беззнаковых целых чисел без использования временной переменной:
...
a ^= b ; b ^= a; a ^=b;
...
или «расширение C» для представления конечных автоматов, таких как:
FSM {
STATE(x) {
...
NEXTSTATE(y);
}
STATE(y) {
...
if (x == 0)
NEXTSTATE(y);
else
NEXTSTATE(x);
}
}
этого можно достичь с помощью следующих макросов:
#define FSM
#define STATE(x) s_##x :
#define NEXTSTATE(x) goto s_##x
В целом, однако, мне не нравятся хитрые приемы, которые делают код излишне сложным для чтения (как пример подкачки), и мне нравятся те, которые делают код более ясным и прямо передают намерение (например, пример FSM) .
C поддерживает цепочку, поэтому вы можете сделать a ^ = b ^ = a ^ = b;
Строго говоря, пример состояния - это галочка препроцессора, а не языка C - можно использовать первый без второго.
OJ: на самом деле то, что вы предлагаете, - это неопределенное поведение из-за правил точки последовательности. Он может работать на большинстве компиляторов, но не является правильным или переносимым.
Использование обмена XOR, как правило, плохая идея, в основном потому, что он работает только для целых чисел, а сглаживание может оказаться большой проблемой. Я почти уверен, что большинство компиляторов оптимизируют его, когда возникает проблема с памятью.
Своп Xor может быть менее эффективным в случае бесплатной регистрации. Любой достойный оптимизатор сделает временную переменную регистром. В зависимости от реализации (и необходимости поддержки параллелизма) своп может фактически использовать реальную память вместо регистра (что будет таким же).
пожалуйста, никогда не делайте этого: en.wikipedia.org/wiki/…
Зачем тебе это делать? кроме как вызвать множество серьезных предупреждений статического анализа. Используйте оператор switch case, заключенный в цикл while.
@OJ: Нет, это неопределенное поведение.
Замена XOR не выполняется, если 'a' является псевдонимом 'b'
OJ: Раньше я делал это с GCC ... пока я не попытался скомпилировать небольшой пример для MIPS и потерял много времени на отладку, потому что он компилировался как-то иначе. Как здесь сказали: не определено.
Компиляторы C реализуют один из нескольких стандартов. Однако наличие стандарта не означает, что определены все аспекты языка. Например, Устройство Даффа - излюбленная «скрытая» функция, которая стала настолько популярной, что современные компиляторы имеют специальный код распознавания, гарантирующий, что методы оптимизации не повлияют на желаемый эффект этого часто используемого шаблона.
В общем, скрытые функции или языковые уловки не приветствуются, поскольку вы работаете на грани бритвы, независимо от того, какой стандарт (-ы) C использует ваш компилятор. Многие такие приемы не работают от одного компилятора к другому, и часто такие функции не работают при переходе от одной версии компилятора от одного производителя к другой версии.
Различные уловки, которые нарушают код C, включают:
Другие проблемы и проблемы, возникающие всякий раз, когда программисты делают предположения о моделях исполнения, которые все указаны в большинстве стандартов C как «зависимое от компилятора» поведение.
Чтобы решить большинство из них, сделайте эти предположения зависимыми от характеристик вашей платформы и опишите каждую платформу в своем собственном заголовке. Выполнение ордера - исключение - никогда не полагайтесь на это; С другой стороны, каждая платформа требует надежного решения.
@Blaisorblade, еще лучше, используйте утверждения времени компиляции, чтобы задокументировать свои предположения таким образом, чтобы компиляция завершилась ошибкой на платформе, где они нарушены.
Я думаю, что следует комбинировать оба, чтобы ваш код работал на нескольких платформах (это было первоначальное намерение), и если макросы функций установлены неправильно, утверждения времени компиляции поймают это. Я не уверен, можно ли, скажем, предположить, что ABI функции можно проверить как утверждения времени компиляции, но это должно быть возможно для большинства других (действительных) (кроме порядка выполнения ;-)).
Функциональные проверки ABI должны выполняться с помощью набора тестов.
Странная векторная индексация:
int v[100]; int index = 10;
/* v[index] it's the same thing as index[v] */
Еще лучше ... char c = 2 ["Привет"]; (c == 'l' после этого).
Не так уж и странно, если учесть, что v [index] == * (v + index) и index [v] == * (index + v)
Пожалуйста, скажите мне, что вы на самом деле не используете это «все время», как задается вопрос!
анонимные структуры и массивы - мои любимые. (см. http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html)
setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));
или же
void myFunction(type* values) {
while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});
его даже можно использовать для создания связанных списков ...
Эту возможность обычно называют «составными литералами». Анонимные (или безымянные) структуры обозначают вложенные структуры, у которых нет имен членов.
согласно моему GCC, «ISO C90 запрещает составные литералы».
«ISO C99 поддерживает составные литералы». «В качестве расширения GCC поддерживает составные литералы в режиме C89 и в C++» (dixit info gcc). Кроме того, «Как расширение GNU, GCC позволяет инициализировать объекты со статической продолжительностью хранения с помощью составных литералов (что невозможно в ISO C99, потому что инициализатор не является константой)».
Указатели на функции. Вы можете использовать таблицу указателей на функции для реализации, например, быстрых интерпретаторов непрямого кода (FORTH) или диспетчеров байтового кода, или для моделирования виртуальных методов, подобных объектно-ориентированному программированию.
Кроме того, в стандартной библиотеке есть скрытые жемчужины, такие как qsort (), bsearch (), strpbrk (), strcspn () [последние два полезны для реализации замены strtok ()].
Недостатком C является то, что подписанное арифметическое переполнение является неопределенным поведением (UB). Поэтому всякий раз, когда вы видите выражение, такое как x + y, оба подписанные int, оно может потенциально переполниться и вызвать UB.
Но если бы они указали поведение при переполнении, это сделало бы его очень медленным на архитектурах, где это не было нормальным поведением. Очень низкие накладные расходы времени выполнения всегда были целью разработки C, и это означало, что многие подобные вещи не определены.
Мне очень хорошо известно, что переполнение Почему - это UB. Это по-прежнему является ошибкой, потому что в стандарте должны быть по крайней мере предусмотрены библиотечные процедуры, которые могут проверять арифметическое переполнение (всех основных операций), не вызывая UB.
@zvrba, «библиотечные подпрограммы, которые могут проверять арифметическое переполнение (всех основных операций)», если бы вы добавили это, вы бы значительно снизили производительность для любых целочисленных арифметических операций. ===== Пример из практики Matlab специально ДОБАВЛЯЕТ функцию управления поведением целочисленного переполнения для переноса или насыщения. И он также выдает исключение всякий раз, когда происходит переполнение ==> Производительность целочисленных операций Matlab: ОЧЕНЬ МЕДЛЕННО. Мой собственный вывод: я думаю, что Matlab - убедительный пример, показывающий, почему вам не нужна проверка переполнения целых чисел.
@zvrba, на мой взгляд, C был разработан ПРЕДПОЛАГАЯ, что всякий раз, когда вы выполняете целочисленную арифметику, вы, программист, проводите строгий анализ, чтобы УБЕДИТЬСЯ, что у вас есть ограниченный-вход-ограниченный-выход (причудливый способ сказать "убедитесь, что ваш ввод и вывод остаются в пределах диапазона ") !! Если вы не проводите такой тщательный анализ, тогда виноват не язык, а программист.
Я сказал, что в стандарте должна быть предусмотрена поддержка библиотека для проверки арифметического переполнения. Каким образом библиотечная процедура может снизить производительность, если вы никогда ее не используете?
Большой минус в том, что GCC не имеет флага для перехвата целочисленных значений со знаком и выдачи исключения времени выполнения. Хотя существуют флаги x86 для обнаружения таких случаев, GCC их не использует. Наличие такого флага позволило бы приложениям, не критичным к производительности (особенно устаревшим), пользоваться преимуществами безопасности с минимальным обзором и рефакторингом кода или без него.
Ничего из этого вовсе не «скрыто». Стандартная библиотека, например, хорошо разрекламирована; если люди предпочитают не читать документацию, они дураки. Указатели на функции просто «продвинутые», они никоим образом не скрыты - с ними связано большинство текстов на языке.
Скрытность относительна, документация относительно абсолютна.
зачем нужна замена strtok?
Я никогда не использовал битовые поля, но они звучат круто для сверхнизкоуровневых вещей.
struct cat {
unsigned int legs:3; // 3 bits for legs (0-4 fit in 3 bits)
unsigned int lives:4; // 4 bits for lives (0-9 fit in 4 bits)
// ...
};
cat make_cat()
{
cat kitty;
kitty.legs = 4;
kitty.lives = 9;
return kitty;
}
Это означает, что sizeof(cat) может быть таким же маленьким, как sizeof(char).
Включены комментарии Аарон и леппи, спасибо, ребята.
Комбинация структур и объединений еще более интересна - во встроенных системах или низкоуровневом коде драйвера. Например, когда вам нравится анализировать регистры SD-карты, вы можете прочитать его с помощью union (1) и прочитать с помощью union (2), который представляет собой структуру битовых полей.
Битовые поля не переносимы - компилятор может свободно выбирать, будут ли в вашем примере для ног выделяться 3 старших бита или 3 младших бита.
Битовые поля - это пример того, где стандарт дает реализациям настолько большую свободу в том, как они реализованы, что на практике они почти бесполезны. Если вам важно, сколько бит занимает значение и как оно хранится, лучше использовать битовые маски.
Битовые поля действительно переносимы, если вы относитесь к ним как к элементам структуры, а не «частям целых чисел». Во встроенной системе с ограниченной памятью важен размер, а не местоположение, поскольку каждый бит драгоценен ... но большинство современных кодировщиков слишком молоды, чтобы помнить об этом. :-)
Ура для битовых полей! Я использую их все время.
@Adam: местоположение может иметь значение во встроенной системе (или где-либо еще), если вы зависите от положения битового поля в его байте. Использование масок устраняет двусмысленность. Аналогично для профсоюзов.
@ComSubVie: с этой точки зрения ничего не переносимо :)
Да, я действительно использовал это для задания, которое у меня было в прошлом году на уроке CS. Мы реализовали алгоритм LZW для работы с определенным ограничением памяти. Единственным вариантом было указать количество битов, которое будет использоваться для каждого поля в структуре, поскольку по умолчанию каждое поле было слишком большим.
Многосимвольные константы:
int x = 'ABCD';
Это устанавливает x в 0x41424344 (или 0x44434241, в зависимости от архитектуры).
Обновлено: Этот метод непереносим, особенно если вы сериализуете int. Однако создание самодокументирующихся перечислений может быть чрезвычайно полезным. например
enum state {
stopped = 'STOP',
running = 'RUN!',
waiting = 'WAIT',
};
Это значительно упрощает работу, если вы просматриваете необработанный дамп памяти и вам нужно определить значение перечисления, не просматривая его.
Я почти уверен, что это не переносная конструкция. Результат создания многосимвольной константы определяется реализацией.
удалите запятую после "WAIT" на случай, если кто-то попытается это сделать.
@ blak3r - спасибо! это напоминает мне еще об одной скрытой особенности. см. здесь: stackoverflow.com/questions/132241/hidden-features-of-c/…
Проблема в том, что их нельзя сравнивать с реальными строками, поскольку в зависимости от порядка байтов они могут храниться в памяти как «STOP» или «POTS».
Он не предназначен для сравнения с настоящими струнами. Дело в том, что вы можете присвоить перечислениям уникальные значения, которые легко читать в дампе памяти.
Запятая была преднамеренной и синтаксически верной.
@Ferruccio - я считаю, что запятая действительна в C99, но не в C89. Это означает, что GCC, вероятно, проглотит его, но некоторые старые компиляторы (или более старые версии GCC) нет.
Комментарии "непереносимых" полностью упускают из виду суть. Это все равно, что критиковать программу за использование INT_MAX только потому, что INT_MAX "непереносимый" :) Эта функция настолько портативна, насколько это необходимо. Константа с несколькими символами - чрезвычайно полезная функция, которая обеспечивает удобочитаемый способ создания уникальных целочисленных идентификаторов.
@Chris Lutz - Я почти уверен, что конечная запятая идет обратно к K&R. Это описано во втором издании (1988 г.).
@Ferruccio: Вы, должно быть, думаете о конечной запятой в списках агрегированных инициализаторов. Что касается конечной запятой в объявлениях перечислений - это недавнее добавление, C99.
Вы забыли HANG или BSOD :-)
Почему бы просто не использовать макросы с определенными числовыми флагами?
@Vince: Макросы, даже для констант, загрязняют пространство имен программы. Аналогичным образом, почему бы не использовать сборку вместо C? ;)
Я тестировал и, по крайней мере, с gcc (пробовал на PowerPC и x86) многобайтовые символы не зависят от порядка байтов. Кто-то пробовал с другим компилятором или проверял стандарт ISO? Приведенный выше ответ кажется неправильным относительно порядка байтов.
Однажды мне показали это в небольшом коде, и я спросил, что он делает:
hexDigit = "0123456789abcdef"[someNybble];
Еще один фаворит:
unsigned char bar[100];
unsigned char *foo = bar;
unsigned char blah = 42[foo];
Первое слишком просто. Я думаю, вы имели в виду someNybble ["0123456789abcdef"]. Второй не компилируется, пока вы не добавите *.
Я думаю, что первый правильный как есть: он преобразует целое число someNybble в диапазоне 0-15 в его шестнадцатеричный эквивалент.
Обе формы правильны для 1-го, но показанная на самом деле не сложная, просто необычная для восприятия, но очевидная для понимания.
Не совсем скрытая функция, но мне это показалось вуду, когда я впервые увидел что-то вроде этого:
void callback(const char *msg, void *data)
{
// do something with msg, e.g.
printf("%s\n", msg);
return;
data = NULL;
}
Причина этой конструкции в том, что если вы скомпилируете это с -Wextra и без строки «data = NULL;» -, gcc выдаст предупреждение о неиспользуемых параметрах. Но с этой бесполезной строкой вы не получите предупреждения.
Обновлено: Я знаю, что есть другие (лучшие) способы предотвратить эти предупреждения. Мне это показалось странным, когда я впервые увидел это.
Разве вместо этого вы не получаете предупреждение о недоступности кода? Почему бы просто не закомментировать «данные» - это также удалит предупреждение о неиспользуемых параметрах.
Нет, я не понял этого в последний раз, когда проверял это, но на самом деле я не использую этот трюк, я предпочитаю использовать неиспользуемый атрибут. Удаление данных не всегда возможно, если вы придерживаетесь подписи, например, потому, что пишете обратные вызовы.
Нет, подпись не меняется. Вы просто делаете это: void callback (const char msg, void * / data * /) Или это: void callback (const char * msg, void *)
С помощью gcc вы можете добавить неиспользуемый атрибут к параметрам: void callback (const char * msg, void * data __attribute __ ((unused)))
В отличие от использования непереносимого синтаксиса атрибут. Вы можете просто указать: (void) data; в функции. Я обычно ставлю его сразу после любых местных (так как они должны быть первыми в c89). Я также стараюсь создать такой макрос: #define UNUSED (x) (void) x, чтобы я мог просто написать: UNUSED (data).
Вы можете использовать '(void) data' где угодно до 'return'. (кажется, Эван это уже сказал)
@Greg Whitfield void callback (const char * msg, void *) {...} здесь не компилируется @all Я знаю, что есть много способов подавить подобные предупреждения
@quinmars - Какой компилятор вы используете? То, что я сказал вам, является стандартным C. Какую ошибку вы получаете?
Я использую gcc, я повторил попытку с g ++, и там действительно работает. Так что, похоже, это особенность C++.
Разве вы не можете просто использовать # неиспользуемые данные?
Скорее уловка компилятора GCC, но вы можете дать компилятору подсказки с указанием ветки (обычно в ядре Linux)
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
см .: http://kerneltrap.org/node/4705
Что мне нравится в этом, так это то, что он также добавляет выразительности некоторым функциям.
void foo(int arg)
{
if (unlikely(arg == 0)) {
do_this();
return;
}
do_that();
...
}
Уловка классная ... :) Особенно с определенными вами макросами. :)
Мне очень нравятся назначенные инициализаторы, добавленные в C99 (и давно поддерживаемые в gcc):
#define FOO 16
#define BAR 3
myStructType_t myStuff[] = {
[FOO] = { foo1, foo2, foo3 },
[BAR] = { bar1, bar2, bar3 },
...
Инициализация массива больше не зависит от позиции. Если вы измените значения FOO или BAR, инициализация массива автоматически будет соответствовать их новому значению.
Синтаксис, который gcc поддерживает в течение долгого времени, не совпадает со стандартным синтаксисом C99.
В некоторых случаях также могут быть полезны автоматические переменные переменного размера. Они были добавлены в nC99 и долгое время поддерживаются в gcc.
void foo(uint32_t extraPadding) {
uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];
В итоге вы получаете буфер в стеке с местом для заголовка протокола фиксированного размера и данных переменного размера. Вы можете получить тот же эффект с помощью alloca (), но этот синтаксис более компактен.
Вы должны убедиться, что extraPadding имеет разумное значение, прежде чем вызывать эту процедуру, иначе вы в конечном итоге взорвете стек. Вам нужно будет проверить аргументы перед вызовом malloc или любого другого метода выделения памяти, так что в этом нет ничего необычного.
Будет ли это работать правильно, если размер байта / символа на целевой платформе не равен 8 битам? Знаю, такие случаи редки, но все же ... :)
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t
Это необязательный элемент в стандарте, но он должен быть скрытой функцией, потому что люди постоянно переопределяют их. Одна база кода, над которой я работал (и работаю до сих пор), имеет несколько переопределений, все с разными идентификаторами. В большинстве случаев это с макросами препроцессора:
#define INT16 short
#define INT32 long
И так далее. Это заставляет меня выдергивать волосы. Просто используйте долбанные стандартные целочисленные определения типов!
Я думаю, что это C99 или около того. Я не нашел портативного способа гарантировать, что они будут поблизости.
Они являются необязательной частью C99, но я не знаю поставщиков компиляторов, которые бы это не реализовали.
stdint.h не является обязательным в C99, но, очевидно, следование стандарту C99 для некоторых поставщиков (кашель Microsoft).
Microsoft Visual C++ также не следует стандарту Ada95. Это не компилятор C99. Это компилятор C++ 97. (Он не всегда соответствует этому стандарту, но несправедливо жаловаться на то, что это не то, на что он не претендует)
@Pete, если хочешь быть аналом: (1) Эта ветка не имеет ничего общего ни с каким продуктом Microsoft. (2) Эта ветка никогда не имела ничего общего с C++. (3) Не существует C++ 97.
Взгляните на azillionmonkeys.com/qed/pstdint.h - почти переносимый stdint.h
@gnud: спасибо за подсказку, но меня беспокоит то, что в этом нет необходимости - большинство компиляторов реализуют стандартные определения типов. Единственный компилятор, который я когда-либо использовал, который не использовал, была старая версия GCC, адаптированная для разработки встроенных VxWorks (старый, например, GCC 2.7).
@Ben Collins: Он указывает, что он почти реализует C++ 98, но не соответствует некоторым требованиям. Кроме того, MSVC не поддерживает C99, особенно stdint.h, который является королевским PITA.
@Anacrolix: Да. Я понял, на что он указывал. Вы, кажется, упускаете мою мысль: это ни к чему. Независимо от того, "поддерживает ли конкретный компилятор C99", на самом деле не имеет никакого отношения к тому, следует ли вам использовать стандартные целочисленные определения типов. Они переносимы и их легко определить, даже если ваш компилятор отстой. Если вам нужно указать определенную целочисленную ширину, тогда следует использовать стандартные определения типов всегда, всегда, независимо от того, откуда берутся определения.
Спасибо большое! Он получает резервную копию, когда я использую заголовки Windows, и вы получаете typedef unsigned long ULONG. Серьезно? Или введите typedef float FLOAT.
Насколько мне известно, в Visual Studio 2010 теперь есть stdint.h из-за высокого спроса на эту особенность C99. Это была королевская PITA, которой не было раньше.
Оператор запятой широко не используется. Конечно, им можно злоупотреблять, но он также может быть очень полезным. Это наиболее распространенное использование:
for (int i=0; i<10; i++, doSomethingElse())
{
/* whatever */
}
Но вы можете использовать этот оператор где угодно. Наблюдать:
int j = (printf("Assigning variable j\n"), getValueFromSomewhere());
Каждый оператор оценивается, но значение выражения будет значением последнего оцененного оператора.
За 20 лет C я такого НИКОГДА не видел!
В C++ его даже можно перегрузить.
может! = должен, конечно. Опасность с перегрузкой заключается в том, что встроенная функция уже применяется ко всему, включая void, поэтому никогда не потерпит неудачу при компиляции из-за отсутствия доступной перегрузки. То есть, дает программисту большую веревку.
Int внутри цикла не будет работать с C: это улучшение C++. Является ли "," той же операцией, что и для (i = 0, j = 10; i <j; j--, i ++)?
Преобразование типов с помощью необычных приведений типов. Хотя это и не скрытая функция, она довольно сложна.
Пример:
Если вам нужно знать, как компилятор хранит float, просто попробуйте следующее:
uint32_t Int;
float flt = 10.5; // say
Int = *(uint32_t *)&flt;
printf ("Float 10.5 is stored internally as %8X\n", Int);
или же
float flt = 10.5; // say
printf ("Float 10.5 is stored internally as %8X\n", *(uint32_t *)&flt);
Обратите внимание на умное использование приведений типов. Преобразование адреса переменной (здесь & flt) в желаемый тип (здесь (uint32_t *)) и извлечение ее содержимого (применение '*').
Это работает и с другой стороны выражения:
*(float *)&Int = flt;
Это также можно сделать с помощью union:
typedef union
{
uint32_t Int;
float flt;
} FloatInt_type;
Это подпадает под «обычное использование, от которого я бы не рекомендовал». Псевдонимы типов и оптимизация не ладят. Вместо этого используйте объединения для ясности как для читателя, так и для компилятора.
Если быть точным, «не ладите» означает, что «этот код на самом деле может быть неправильно скомпилирован», потому что это неопределенное поведение в C.
Мне понравились структуры переменного размера, которые вы могли сделать:
typedef struct {
unsigned int size;
char buffer[1];
} tSizedBuffer;
tSizedBuffer *buff = (tSizedBuffer*)(malloc(sizeof(tSizedBuffer) + 99));
// can now refer to buff->buffer[0..99].
Также макрос offsetof, который сейчас находится в ANSI C, но когда я впервые увидел его, это было чудо. Он в основном использует оператор адресации (&) для преобразования нулевого указателя в структурную переменную.
Это не соответствует стандарту; В C99 есть «гибкие элементы массива», которые достигают того же результата и соответствуют требованиям. (Удалите 1 из объявления, чтобы использовать гибкий элемент массива.)
Моя любимая «скрытая» функция C - использование% n в printf для обратной записи в стек. Обычно printf извлекает значения параметров из стека на основе строки формата, но% n может записать их обратно.
Ознакомьтесь с разделом 3.4.2 здесь. Может привести к множеству неприятных уязвимостей.
ссылка больше не работает, на самом деле кажется, что сам сайт не работает. Можете дать еще одну ссылку?
@thequark: Любая статья об "уязвимостях строки формата" будет содержать некоторую информацию .. (например, crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf) .. Однако из-за характера поля сами сайты безопасности немного нестабильны, и трудно найти настоящие академические статьи by (с реализацией).
gcc имеет ряд расширений для языка C, которые мне нравятся, которые можно найти здесь. Некоторые из моих любимых - атрибуты функции. Один чрезвычайно полезный пример - атрибут формата. Это можно использовать, если вы определяете пользовательскую функцию, которая принимает строку формата printf. Если вы включите этот атрибут функции, gcc проверит ваши аргументы, чтобы убедиться, что ваша строка формата и аргументы совпадают, и при необходимости будет генерировать предупреждения или ошибки.
int my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 2, 3)));
Я раньше объявлял некоторые переменные с помощью ключевого слова register, чтобы ускорить процесс. Это подскажет компилятору C использовать регистр ЦП в качестве локального хранилища. Скорее всего, в этом больше нет необходимости, поскольку современные компиляторы C делают это автоматически.
Более того, компилятор C лучше вас знает, какие переменные больше всего выиграют от нахождения в регистре. Большинство современных компиляторов достаточно умны, чтобы полностью игнорировать ключевое слово register, но если бы они действительно обратили на него внимание, это, вероятно, замедлило бы ваш код.
Я почти уверен, что некоторые компиляторы не позволяют вам принимать адрес переменной, объявленной с помощью register. Так что это полезно для того, чтобы ваши намерения были ясны.
На самом деле, когда вы проверяете это в очень конкретном сценарии с оптимизацией кода и, возможно, смешанной сборкой, это ключевое слово действительно имеет свои достоинства и может значительно увеличить скорость. Конечно, это крайние случаи (один мой пост на EE показывает, как и когда это полезно, но это было давно).
Учитывая комбинацию микроконтроллера и чтения вывода сборки, это все равно может быть полезно в каком-то странном случае.
инициализация структуры до нуля
struct mystruct a = {0};
это обнулит все элементы конструкции.
Обнуляет ли он и весь массив?
Однако он не обнуляет отступы, если они есть.
@drhorrible: вы можете сделать int array [50] = {0}; Например. Но только при декларации (думаю.)
Разве это не делает что-то неопределенное, если структура содержит нецелочисленные типы (например, числа с плавающей запятой и двойные)?
@simonn, нет, он не выполняет неопределенное поведение, если структура содержит нецелочисленные типы. memset с 0 в памяти float / double будет по-прежнему равным нулю, когда вы интерпретируете float / double (float / double специально созданы таким образом).
@Trevor Я думал, что эффект, который это оказывает на числа с плавающей запятой, - это «все нулевые байты», что во всех разумных случаях даст вам значение с плавающей запятой, равное 0,0, но это все еще определяется реализацией.
@Andrew: memset / calloc выполняет «все нулевые байты» (т.е. физические нули), что действительно не определено для всех типов. { 0 } гарантированно инициализирует все с соответствующими нулевыми значениями логичный. Например, гарантируется, что указатели получат свои правильные нулевые значения, даже если нулевым значением на данной платформе является 0xBAADFOOD.
@AndreyT: Не могли бы вы подробнее рассказать о различиях между логическими и физическими нулями?
@nvl: вы получаете физический ноль, когда вы просто принудительно устанавливаете всю память, занятую объектом, в состояние «все биты-ноль». Это то, что делает memset (с 0 в качестве второго аргумента). Вы получаете нулевое значение логичный, когда инициализируете / назначаете 0 (или { 0 }) объекту в исходном коде. Эти два вида нулей не обязательно дают одинаковый результат. Как в примере с указателем. Когда вы выполняете memset для указателя, вы получаете указатель 0x0000. Но когда вы назначаете 0 указателю, вы получаете значение нулевого указателя, который на физическом уровне может быть 0xBAADF00D или чем-то еще.
Другими словами, значение физический является явным битовым шаблоном, который хранится в памяти. Значение Логический - это то, как этот битовый шаблон интерпретируется на программном уровне.
@AndreyT Я понимаю, что такое физический ноль. Но не может полностью переварить эти логические нули. Любая другая структура данных, кроме указателя, которая имеет разные логические / физические представления?
@nvl: Ну, на практике разница часто носит чисто концептуальный характер. Но теоретически он может быть практически у любого типа. Например, double. Обычно это реализовано в соответствии со стандартом IEEE-754, в котором логический ноль и физический ноль совпадают. Но язык не требует IEEE-754. Таким образом, может случиться так, что когда вы сделаете double d = 0; (логический ноль), физически некоторые биты в памяти, занятой d, не будут равны нулю.
То же самое со значениями bool для другого примера. Если вы используете bool b = false; (или, что то же самое, bool b = 0;), это не обязательно означает, что в физической памяти b будет обнулен (хотя на практике это обычно так).
Понятно. Спасибо. Теперь я вспоминаю представление с плавающей запятой. Имеет смысл.
@AndreyT: Отличные комментарии. +1
Назначение структуры - это круто. Многие люди, кажется, не понимают, что структуры тоже являются значениями, и их можно назначать повсюду, нет необходимости использовать memcpy(), когда простое присваивание помогает.
Например, рассмотрим некоторую воображаемую библиотеку 2D-графики, она может определять тип для представления (целочисленной) координаты экрана:
typedef struct {
int x;
int y;
} Point;
Теперь вы делаете вещи, которые могут показаться «неправильными», например, пишете функцию, которая создает точку, инициализированную из аргументов функции, и возвращает ее, например:
Point point_new(int x, int y)
{
Point p;
p.x = x;
p.y = y;
return p;
}
Это безопасно, если (конечно) возвращаемое значение копируется по значению с использованием назначения структуры:
Point origin;
origin = point_new(0, 0);
Таким образом, вы можете написать довольно чистый и объектно-ориентированный код на простом стандартном языке C.
Конечно, такой способ обхода больших структур влияет на производительность; это часто бывает полезно (и это действительно то, что многие люди не осознают, что вы можете это сделать), но вам нужно подумать, лучше ли передавать указатели.
Конечно, есть мощь. Компилятор также вполне может обнаружить использование и оптимизировать его.
Будьте осторожны, если какие-либо элементы являются указателями, так как вы будете копировать сами указатели, а не их содержимое. Конечно, то же самое верно и в случае использования memcpy ().
Компилятор не может оптимизировать это преобразование по значению, передаваемому по ссылке, если он не может выполнять глобальную оптимизацию.
Вероятно, стоит отметить, что в C++ стандарт специально позволяет оптимизировать копию (стандарт должен позволять компиляторам реализовать его, потому что это означает, что конструктор копирования, который может иметь побочные эффекты, не может быть вызван), и поскольку большинство компиляторов C++ также являются компиляторами C, есть большая вероятность, что ваш компилятор выполняет эту оптимизацию.
Я исправил последний вызов «конструктора», он вызывал Point (0, 0), что, конечно же, неправильно, point_new () - это то, что нужно. Ой.
Инициализация структуры C99 предназначена для оптимизации, в то время как этот код полагается на тот факт, что компилятор МОЖЕТ встроить функцию И МОЖЕТ отбросить копию.
Ну, я никогда этим не пользовался и не уверен, рекомендую ли я его когда-либо, но я считаю, что этот вопрос был бы неполным без упоминания совместный трюк. Саймона Татхама.
Утверждения времени компиляции, как уже обсуждалось здесь.
//--- size of static_assertion array is negative if condition is not met
#define STATIC_ASSERT(condition) \
typedef struct { \
char static_assertion[condition ? 1 : -1]; \
} static_assertion_t
//--- ensure structure fits in
STATIC_ASSERT(sizeof(mystruct_t) <= 4096);
Макросы с переменными аргументами в стиле C99, также известные как
#define ERR(name, fmt, ...) fprintf(stderr, "ERROR " #name ": " fmt "\n", \
__VAR_ARGS__)
который будет использоваться как
ERR(errCantOpen, "File %s cannot be opened", filename);
Здесь я также использую оператор строкового преобразования и объединение строковых констант, другие функции, которые мне очень нравятся.
У вас есть дополнительная буква R в VA_ARGS.
Отрывок:
In this page, you will find a list of interesting C programming questions/puzzles, These programs listed are the ones which I have received as e-mail forwards from my friends, a few I read in some books, a few from the internet, and a few from my coding experiences in C.
При инициализации массивов или перечислений вы можете поставить запятую после последнего элемента в списке инициализатора. например:
int x[] = { 1, 2, 3, };
enum foo { bar, baz, boom, };
Это было сделано для того, чтобы, если вы генерируете код автоматически, вам не нужно беспокоиться об удалении последней запятой.
Это также важно в среде с несколькими разработчиками, где, например, Эрик добавляет «baz», а затем Джордж добавляет «бум». Если Эрик решит вытащить свой код для следующей сборки проекта, он все равно будет компилироваться с изменениями Джорджа. Очень важно для управления исходным кодом нескольких ветвей и перекрывающихся графиков разработки.
Перечисления могут быть C99. Инициализаторы массивов и конечная запятая - K&R.
Простые перечисления были в c89, AFAIK. По крайней мере, они существуют уже много лет.
Gcc (c) имеет несколько забавных функций, которые вы можете включить, например, объявления вложенных функций и форму a?: B оператора?:, Который возвращает a, если a не ложно.
Скажем, у вас есть структура с членами одного типа:
struct Point {
float x;
float y;
float z;
};
Вы можете преобразовать его экземпляры в указатель с плавающей запятой и использовать индексы массива:
Point a;
int sum = 0, i = 0;
for( ; i < 3; i++)
sum += ((float*)a)[i];
Довольно элементарно, но полезно при написании лаконичного кода.
Вы уверены, что это портативный? Я думал, что стандарты C не дают никаких гарантий относительно выравнивания структуры, кроме того, что первый элемент находится со смещением 0. Между элементами могут быть промежутки. Т.е. sizeof (Point) не обязательно будет sizeof (float) * 3.
@jmtd, Верно. На практике его вполне достаточно «портативного», чтобы доставить вам неприятности. Смещение любого члена, кроме первого, является поведением, определяемым реализацией, и не обязательно должно иметь такую же эффективную упаковку, как массив этого типа. На практике вполне вероятно, что он имеет ту же упаковку, что и массив, поэтому этот код будет работать до тех пор, пока он не будет перенесен на платформу следующий, где он загадочным образом выйдет из строя. То же самое произошло с обычной реализацией MD5 при переносе на 64-битную версию: она скомпилировалась и запустилась, но получила другой ответ.
C99 имеет отличную инициализацию структуры любого порядка.
struct foo{
int x;
int y;
char* name;
};
void main(){
struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}
Структуры переменного размера, встречающиеся в обычных библиотеках преобразователей среди других мест.
struct foo
{
int a;
int b;
char b[1]; // using [0] is no longer correct
// must come at end
};
char *str = "abcdef";
int len = strlen(str);
struct foo *bar = malloc(sizeof(foo) + len);
strcpy(bar.b, str); // try and stop me!
В C99 правильный способ объявить это: char b[];, который имеет то преимущество, что вам не нужно вычитать 1*sizeof b[0] из размера вашей структуры.
Разве доступ к b [] не превышает объявленного размера undefined, независимо от того, существует ли для него выделенное пространство? Я бы подумал, что было бы чище использовать char b [MAX_ARRAY_SIZE], а затем вычесть MAX_ARRAY_SIZE из распределения. Еще лучше было бы, если бы массивы нулевого размера были разрешены в первую очередь, и компиляторы должны были бы рассматривать их как указатель на место начала массива, но без ограничения размера.
Вот три хороших варианта в gcc:
__FILE__
__FUNCTION__
__LINE__
Оберните malloc и realloc следующим образом:
#ifdef _DEBUG
#define mmalloc(bytes) malloc(bytes);printf("malloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#define mrealloc(pointer, bytes) realloc(pointer, bytes);printf("realloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#else //_DEBUG
#define mmalloc(bytes) malloc(bytes)
#define mrealloc(pointer, bytes) realloc(pointer, bytes)
Фактически, вот мой полный арсенол (BailIfNot предназначен для OO c):
#ifdef _DEBUG
#define mmalloc(bytes) malloc(bytes);printf("malloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#define mrealloc(pointer, bytes) realloc(pointer, bytes);printf("realloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#define BAILIFNOT(Node, Check) if (Node->type != Check) return 0;
#define NULLCHECK(var) if (var == NULL) setError(__FILE__, __LINE__, "Null exception", " var ", FATAL);
#define ASSERT(n) if ( ! ( n ) ) { printf("<ASSERT FAILURE@%s:%d>", __FILE__, __LINE__); fflush(0); __asm("int $0x3"); }
#define TRACE(n) printf("trace: %s <%s@%d>\n", n, __FILE__, __LINE__);fflush(0);
#else //_DEBUG
#define mmalloc(bytes) malloc(bytes)
#define mrealloc(pointer, bytes) realloc(pointer, bytes)
#define BAILIFNOT(Node, Check) {}
#define NULLCHECK(var) {}
#define ASSERT(n) {}
#define TRACE(n) {}
#endif //_DEBUG
Вот пример вывода:
malloc: 12 <hash.c@298>
trace: nodeCreate <hash.c@302>
malloc: 5 <hash.c@308>
malloc: 16 <hash.c@316>
malloc: 256 <hash.c@320>
trace: dataLoadHead <hash.c@441>
malloc: 270 <hash.c@463>
malloc: 262144 <hash.c@467>
trace: dataLoadRecursive <hash.c@404>
пожалуйста, не нравится ... например, в противном случае правильный код if (something) mmaloc(); else otherthing; не будет компилироваться, если _DEBUG определен.
вам нужна запятая в макросах malloc, а не точка с запятой (по причинам, описанным @fortran). Однако это игнорирует возвращаемое значение (но опять же, я не уверен, почему эти макросы желательны).
Я только что прочитал этот статья. Он имеет некоторые «скрытые функции» C и некоторых других языков.
О боже! все они вносят свой вклад в stackoverflow, извините (я здесь новичок и не заметил, что есть раздел скрытых функций) ... В любом случае, он может работать как справочник и краткое руководство по этим темам.
Объектно-ориентированные макросы C: Вам нужен конструктор (init), деструктор (dispose), равный (равный), копировальный (copy) и некоторый прототип для создания экземпляра (prototype).
С помощью объявления вам нужно объявить постоянный прототип, который будет копироваться и производиться от него. Тогда можно делать C_OO_NEW. При необходимости я могу опубликовать больше примеров. LibPurple - это большая объектно-ориентированная база кода C с системой обратного вызова (если вы хотите увидеть ее в использовании)
#define C_copy(to, from) to->copy(to, from)
#define true 1
#define false 0
#define C_OO_PROTOTYPE(type)\
void type##_init (struct type##_struct *my);\
void type##_dispose (struct type##_struct *my);\
char type##_equal (struct type##_struct *my, struct type##_struct *yours); \
struct type##_struct * type##_copy (struct type##_struct *my, struct type##_struct *from); \
const type type##__prototype = {type##_init, type##_dispose, type##_equal, type##_copy
#define C_OO_OVERHEAD(type)\
void (*init) (struct type##_struct *my);\
void (*dispose) (struct type##_struct *my);\
char (*equal) (struct type##_struct *my, struct type##_struct *yours); \
struct type##_struct *(*copy) (struct type##_struct *my, struct type##_struct *from);
#define C_OO_IN(ret, type, function, ...) ret (* function ) (struct type##_struct *my, __VA_ARGS__);
#define C_OO_OUT(ret, type, function, ...) ret type##_##function (struct type##_struct *my, __VA_ARGS__);
#define C_OO_PNEW(type, instance)\
instance = ( type *) malloc(sizeof( type ));\
memcpy(instance, & type##__prototype, sizeof( type ));
#define C_OO_NEW(type, instance)\
type instance;\
memcpy(&instance, & type ## __prototype, sizeof(type));
#define C_OO_DELETE(instance)\
instance->dispose(instance);\
free(instance);
#define C_OO_INIT(type) void type##_init (struct type##_struct *my){return;}
#define C_OO_DISPOSE(type) void type##_dispose (struct type##_struct *my){return;}
#define C_OO_EQUAL(type) char type##_equal (struct type##_struct *my, struct type##_struct *yours){return 0;}
#define C_OO_COPY(type) struct type##_struct * type##_copy (struct type##_struct *my, struct type##_struct *from){return 0;}
Мне нравится оператор typeof (). Он работает как sizeof () в том смысле, что разрешается во время компиляции. Вместо того, чтобы возвращать количество байтов, он возвращает тип. Это полезно, когда вам нужно объявить переменную того же типа, что и какая-либо другая переменная, независимо от типа.
typeof(foo) copy_of_foo; //declare bar to be a variable of the same type as foo
copy_of_foo = foo; //now copy_of_foo has a backup of foo, for any type
Я не уверен, что это может быть просто расширение gcc.
в том же семействе есть также offsetof (), ну, это макрос, но все равно приятно.
А если хотите: #define countof(array) (sizeof (array) / sizeof (array[0]));)
(скрытая) особенность, которая "шокировала" меня, когда я впервые увидел, связана с printf. эта функция позволяет вам использовать переменные для форматирования самих спецификаторов формата. ищи код, увидишь лучше:
#include <stdio.h>
int main() {
int a = 3;
float b = 6.412355;
printf("%.*f\n",a,b);
return 0;
}
символ * достигает этого эффекта.
Для очистки входного буфера нельзя использовать fflush(stdin). Правильный способ выглядит следующим образом: scanf("%*[^\n]%*c")
Это отбросит все из входного буфера.
@ Томас Сенарт. У вас есть ссылка на это? Я полностью согласен с тем, что вы не можете использовать fflush (stdin) для очистки буфера, который работает только в Windows c компилятор, но не работает в gcc.
Используйте NaN для связанных вычислений / возврата ошибок:
// # включаем <stdint.h>
статический uint64_t iNaN = 0xFFF8000000000000;
константный двойной NaN = * (двойной *) & iNaN; // тихий NaN
Внутренняя функция может возвращать NaN как флаг ошибки: ее можно безопасно использовать в любых вычислениях, и результатом всегда будет NaN.
примечание: тестирование на NaN сложно, так как NaN! = NaN ... используйте isnan (x) или сверните свой собственный. x! = x математически правильно, если x равно NaN, но имеет тенденцию быть оптимизированным некоторыми компиляторами
Вместо того, чтобы использовать шестнадцатеричные константы, я предпочитаю получать NaN несигнальным делением нуля на ноль с плавающей запятой.
Я обнаружил это только после 15 с лишним лет программирования на C:
struct SomeStruct
{
unsigned a : 5;
unsigned b : 1;
unsigned c : 7;
};
Битовые поля! Число после двоеточия - это количество битов, требуемых члену, с членами, упакованными в указанный тип, поэтому приведенное выше будет выглядеть следующим образом, если unsigned составляет 16 бит:
xxxc cccc ccba aaaa
Skizz
Проверка предположений во время компиляции с использованием перечислений: Глупый пример, но может быть действительно полезен для библиотек с константами, настраиваемыми во время компиляции.
#define D 1
#define DD 2
enum CompileTimeCheck
{
MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)),
MAKE_SURE_DD_IS_POW2 = 1/((((DD) - 1) & (DD)) == 0)
};
+1 Аккуратно. Раньше я использовал макрос CompilerAssert от Microsoft, но и ваш неплохой. (#define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1])
Мне нравится метод перечисления. Подход, который я использовал раньше, использовал преимущество исключения мертвого кода: «if (something_bad) {void BLORG_IS_WOOZLED (void); BLORG_IS_WOOZLED ();}», который не приводил к ошибкам до времени компоновки, хотя он давал преимущество, позволяя программист узнает через сообщение об ошибке, что blorg был взорван.
intptr_t для объявления переменных типа указатель. Специфично для C99 и объявлено в stdint.h
Лямбды (например, анонимные функции) в GCC:
#define lambda(return_type, function_body) \
({ return_type fn function_body fn })
Это можно использовать как:
lambda (int, (int x, int y) { return x > y; })(1, 2)
Что расширяется в:
({ int fn (int x, int y) { return x > y } fn; })(1, 2)
Постоянная конкатенация строк
Я был очень удивлен, не увидев этого уже в ответах, поскольку все известные мне компиляторы поддерживают его, но многие программисты, кажется, игнорируют это. Иногда это действительно удобно, и не только при написании макросов.
Пример использования в моем текущем коде:
У меня есть #define PATH "/some/path/" в файле конфигурации (на самом деле он устанавливается в make-файле). Теперь я хочу построить полный путь, включая имена файлов, для открытия ресурсов. Это просто идет на:
fd = open(PATH "/file", flags);
Вместо ужасного, но очень распространенного:
char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);
Обратите внимание, что обычное ужасное решение:
Также полезно разделить строковую константу на несколько строк исходного кода без использования грязного символа `\`.
Мне нравятся __LINE__ и __FILE__. Смотрите здесь: http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
При использовании sscanf вы можете использовать% n, чтобы узнать, где вам следует продолжить чтение:
sscanf ( string, "%d%n", &number, &length );
string += length;
По-видимому, вы не можете добавить еще один ответ, поэтому я включу сюда второй, вы можете использовать «&&» и «||» как условные:
#include <stdio.h>
#include <stdlib.h>
int main()
{
1 || puts("Hello\n");
0 || puts("Hi\n");
1 && puts("ROFL\n");
0 && puts("LOL\n");
exit( 0 );
}
Этот код выведет:
Hi ROFL
Я обнаружил недавно 0 битовых полей.
struct {
int a:3;
int b:2;
int :0;
int c:4;
int d:3;
};
что даст макет
000aaabb 0ccccddd
вместо без: 0;
0000aaab bccccddd
Поле ширины 0 указывает, что следующие битовые поля должны быть установлены на следующем атомарном объекте (char).
Размер указателей на функции нестандартный. По крайней мере, не в книге K&R. Несмотря на то, что он говорит о размере других типов указателей, но (я думаю) sizeof указателя на функцию является неопределенным поведением.
Также sizeof является оператором времени компиляции, я вижу, что многие люди спрашивают, является ли sizeof функцией или оператором на онлайн-форумах.
Одна ошибка, которую я видел, заключается в следующем (упрощенный пример):
int j;
int i;
j = sizeof(i++)
приращение i не будет выполнено, поскольку sizeof оценивается во время компиляции. Программист намеревался взломать обе операции, приращение i и вычисление sizeof в одном операторе.
Приоритет операторов в C управляет порядком ассоциации, а не порядком оценки. Например, если у вас есть три функции f, g, h, каждая из которых возвращает int, и их выражение выглядит так:
f() + g() * h()
Стандарт C не дает правил о порядке вычисления этих функций. Результат g и h будет умножен перед добавлением результата f. Это может привести к ошибке, если функции разделяют состояние и вычисление зависит от порядка оценки этих функций. Это может привести к проблемам с переносимостью.
Стив Уэбб указал на макросы __LINE__ и __FILE__. Это напоминает мне о том, как на моей предыдущей работе я взломал их, чтобы вести журнал в памяти.
Я работал над устройством, на котором не было порта для передачи информации журнала с устройства на ПК, используемый для отладки. Можно было использовать точки останова, чтобы остановить и узнать состояние программы с помощью отладчика, но не было никакой информации о системной трассировке.
Поскольку все вызовы журналов отладки были фактически одним глобальным макросом, мы изменили этот макрос, чтобы выгрузить имя файла и номер строки в глобальный массив. Этот массив содержал серию имен файлов и номеров строк, показывающих, какие отладочные вызовы были вызваны, давая четкое представление о трассировке выполнения (но не фактическое сообщение журнала). Можно приостановить выполнение отладчиком, сбросить эти байты в локальный файл, а затем сопоставить эту информацию с базой кода с помощью сценариев. Это стало возможным, потому что у нас были строгие правила кодирования, поэтому мы могли внести изменения в механизм ведения журнала в одном файле.
При сравнении переменной с литералом лучше поместить литерал в левый оператора ==, чтобы убедиться, что компилятор выдает ошибку, когда вы по ошибке используете вместо этого оператор присваивания.
if (0 == count) {
...
}
На первый взгляд это может показаться странным, но это может избавить от головной боли (например, если вы случайно набрали if (count = 0)).
Это старый трюк, который неукоснительно используется некоторыми людьми, но я считаю, что многие компиляторы выдают предупреждение, когда видят, что if (count = 0) делает его несколько избыточным.
В Visual Studio вы можете выделить свои собственные определенные типы.
Для этого создайте файл с именем «usertype.dat» в папке «Commom7 / IDE». Содержимым этого файла должны быть типы, которые вы хотите выделить. Например:
// содержимое usertype.dat
int8_t
int16_t
int32_t
int64_t
uint8_t
uint16_t
uint32_t
uint64_t
float32_t
float64_t
char_t
Часто забываемый спецификатор %n в строке формата printf иногда может быть весьма практичным. % n возвращает текущую позицию воображаемого курсора, используемого при форматировании вывода printf.
int pos1, pos2;
char *string_of_unknown_length = "we don't care about the length of this";
printf("Write text of unknown %n(%s)%n text\n", &pos1, string_of_unknown_length, &pos2);
printf("%*s\%*s/\n", pos1, " ", pos2-pos1-2, " ");
printf("%*s", pos1+1, " ");
for(int i=pos1+1; i<pos2-1; i++)
putc('-', stdout);
putc('\n', stdout);
будет следующий вывод
Write text of unknown (we don't care about the length of this) text
\ /
--------------------------------------
Конечно, это немного надумано, но может быть использовано при составлении хороших отчетов.
Как насчет использования while (0) внутри переключателя, чтобы вы могли использовать операторы continue, такие как break :-)
void sw(int s)
{
switch (s) while (0) {
case 0:
printf("zero\n");
continue;
case 1:
printf("one\n");
continue;
default:
printf("something else\n");
continue;
}
}
Было бы здорово, если бы вы / кто-то отредактировали «вопрос», чтобы указать на выбор лучшие скрытые функции, например, в версиях этого вопроса для C# и Perl.