Скрытые возможности C

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

Было бы здорово, если бы вы / кто-то отредактировали «вопрос», чтобы указать на выбор лучшие скрытые функции, например, в версиях этого вопроса для C# и Perl.

Donal Fellows 26.05.2010 17:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
141
1
83 115
56
Перейти к ответу Данный вопрос помечен как решенный

Ответы 56

использование INT (3) для установки точки останова в коде - мой фаворит на все времена

Я не думаю, что это портативный. Он будет работать на x86, но как насчет других платформ?

Cristian Ciupitu 25.09.2008 23:25

Понятия не имею - вы должны задать вопрос по этому поводу

Dror Helper 08.12.2008 19:48

Это хороший метод, и он специфичен для X86 (хотя, вероятно, есть аналогичные методы на других платформах). Однако это не функция C. Это зависит от нестандартных расширений C или вызовов библиотеки.

Ferruccio 22.06.2009 16:28

В GCC есть __builtin_trap и для MSVC __debugbreak, которые будут работать на любой поддерживаемой архитектуре.

Axel Gneiting 07.06.2010 23:05

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

Но на самом деле, как часто вам нужно компилировать свой код с помощью другого компилятора?

Joe D 07.06.2010 22:26

@Joe D, если это кроссплатформенный проект, такой как Windows / OSX / Linux, возможно, немного, а также есть разные арки, такие как x86 vs x86_64 и т. д.

Pharaun 11.11.2010 20:13

@JoeD Если только вы не участвуете в очень ограниченном проекте, который счастлив выйти замуж за одного поставщика компилятора, очень. Возможно, вы захотите избежать фактической смены компиляторов, но вы хотите, чтобы эта опция оставалась открытой. Однако со встроенными системами у вас не всегда есть выбор. АГС, АСС.

XTL 17.02.2012 11:34

Чередующиеся структуры, такие как Устройство Даффа:

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);
    }
}

Я этого не понимаю. Я добавил ссылку, она отображается в превью, но не в финальном посте ...

ComSubVie 25.09.2008 18:00

Это неправильная функция strcpy - эта реализация предполагает, что «to» - это регистр с отображением в память.

Adam Rosenfield 25.09.2008 18:23

@ComSubVie: вы создали ссылку, но еще не создали ее. К ответу нужно добавить что-то вроде этого: «[статья в Википедии] [1] на устройстве Даффа». Лично мне не нравится нотация Markdown для ссылок, я обычно использую теги привязки HTML напрямую.

DGentry 25.09.2008 20:36

@ComSubVie, любой, кто использует Устройство Даффа, является скрипачом, который видел Устройство Даффа и думал, что их код будет выглядеть 1337, если они будут использовать Устройство Даффа. (1.) Устройство Даффа не предлагает никакого увеличения производительности на современных процессорах, потому что современные процессоры имеют нулевые накладные расходы зацикливания. Другими словами, это устаревший фрагмент кода. (2.) Даже если ваш процессор не поддерживает цикл с нулевыми накладными расходами, он, вероятно, будет иметь что-то вроде SSE / altivec / vector-processing, что заставит ваше Устройство Даффа посрамить при использовании memcpy (). (3.) Я упоминал еще о том, что выполнение memcpy () duff's бесполезно?

Trevor Boyd Smith 11.06.2009 17:50

@ComSubVie, познакомься с моим кулаком смерти (en.wikipedia.org/wiki/…)

Trevor Boyd Smith 11.06.2009 17:52

Это ... УЖАСНО. зачем кому-то делать это с плохим беззащитным кодом?

Brian Postow 03.11.2009 18:11

@Trevor: значит, только скриптовые дети программируют 8051 и микроконтроллеры PIC, верно?

SF. 19.02.2010 14:13

@Trevor Boyd Smith: Хотя устройство Даффа кажется устаревшим, это все еще историческое любопытство, которое подтверждает ответ ComSubVie. Во всяком случае, цитирую Википедию: «Когда многочисленные экземпляры устройства Даффа были удалены с сервера XFree86 в версии 4.0, производительность значительно улучшилась». ...

paercebal 08.05.2010 13:01

В Symbian мы однажды оценили различные циклы для быстрого кодирования пикселей; устройство даффа на ассемблере было самым быстрым. Так что сегодня он по-прежнему актуален для основных ядер ARM на ваших смартфонах.

Will 25.10.2010 12:37

@Trevor: Хотя устройство Даффа может быть устаревшим (это не так), его концепция (разворачивание цикла) - нет.

Michael Foukarakis 11.08.2011 09:45

Ранние версии gcc пытались запустить игру всякий раз, когда встречали "#pragma" в исходном коде. См. Также здесь.

#pragma Идентификаторы яда GCC Эта директива запрещает использование идентификаторов в программе. Отравленные идентификаторы не могут быть # ifdef'd или # undef'd, и попытка использовать их для чего-либо приведет к ошибке. Идентификаторы - это список, разделенный пробелами.

squadette 12.07.2010 14:14

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;

OJ. 25.09.2008 14:06

Строго говоря, пример состояния - это галочка препроцессора, а не языка C - можно использовать первый без второго.

Greg Whitfield 25.09.2008 17:10

OJ: на самом деле то, что вы предлагаете, - это неопределенное поведение из-за правил точки последовательности. Он может работать на большинстве компиляторов, но не является правильным или переносимым.

Evan Teran 25.09.2008 18:14

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

user9282 25.09.2008 18:24

Своп Xor может быть менее эффективным в случае бесплатной регистрации. Любой достойный оптимизатор сделает временную переменную регистром. В зависимости от реализации (и необходимости поддержки параллелизма) своп может фактически использовать реальную память вместо регистра (что будет таким же).

Paul de Vrieze 17.10.2008 13:49

пожалуйста, никогда не делайте этого: en.wikipedia.org/wiki/…

Christian Oudard 02.01.2009 21:55

Зачем тебе это делать? кроме как вызвать множество серьезных предупреждений статического анализа. Используйте оператор switch case, заключенный в цикл while.

Oliver 09.07.2009 20:23

@OJ: Нет, это неопределенное поведение.

AnT 28.10.2009 13:13

Замена XOR не выполняется, если 'a' является псевдонимом 'b'

Adrian Panasiuk 05.11.2009 22:43

OJ: Раньше я делал это с GCC ... пока я не попытался скомпилировать небольшой пример для MIPS и потерял много времени на отладку, потому что он компилировался как-то иначе. Как здесь сказали: не определено.

dbarbosa 17.09.2010 08:04

Компиляторы C реализуют один из нескольких стандартов. Однако наличие стандарта не означает, что определены все аспекты языка. Например, Устройство Даффа - излюбленная «скрытая» функция, которая стала настолько популярной, что современные компиляторы имеют специальный код распознавания, гарантирующий, что методы оптимизации не повлияют на желаемый эффект этого часто используемого шаблона.

В общем, скрытые функции или языковые уловки не приветствуются, поскольку вы работаете на грани бритвы, независимо от того, какой стандарт (-ы) C использует ваш компилятор. Многие такие приемы не работают от одного компилятора к другому, и часто такие функции не работают при переходе от одной версии компилятора от одного производителя к другой версии.

Различные уловки, которые нарушают код C, включают:

  1. В зависимости от того, как компилятор размещает структуры в памяти.
  2. Предположения на порядок байтов целых чисел / чисел с плавающей запятой.
  3. Предположения о функции ABIs.
  4. Предположения о направлении роста кадров стека.
  5. Предположения о порядке выполнения внутри операторов.
  6. Предположения о порядке выполнения операторов в аргументах функции.
  7. Предположения о размере битов или точности типов short, int, long, float и double.

Другие проблемы и проблемы, возникающие всякий раз, когда программисты делают предположения о моделях исполнения, которые все указаны в большинстве стандартов C как «зависимое от компилятора» поведение.

Чтобы решить большинство из них, сделайте эти предположения зависимыми от характеристик вашей платформы и опишите каждую платформу в своем собственном заголовке. Выполнение ордера - исключение - никогда не полагайтесь на это; С другой стороны, каждая платформа требует надежного решения.

Blaisorblade 19.01.2009 03:17

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

RBerteig 01.08.2010 04:06

Я думаю, что следует комбинировать оба, чтобы ваш код работал на нескольких платформах (это было первоначальное намерение), и если макросы функций установлены неправильно, утверждения времени компиляции поймают это. Я не уверен, можно ли, скажем, предположить, что ABI функции можно проверить как утверждения времени компиляции, но это должно быть возможно для большинства других (действительных) (кроме порядка выполнения ;-)).

Blaisorblade 02.08.2010 17:01

Функциональные проверки ABI должны выполняться с помощью набора тестов.

dolmen 29.03.2011 01:30

Странная векторная индексация:

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */

Еще лучше ... char c = 2 ["Привет"]; (c == 'l' после этого).

yrp 25.09.2008 14:20

Не так уж и странно, если учесть, что v [index] == * (v + index) и index [v] == * (index + v)

Ferruccio 25.09.2008 18:36

Пожалуйста, скажите мне, что вы на самом деле не используете это «все время», как задается вопрос!

Tryke 26.09.2008 00:52

анонимные структуры и массивы - мои любимые. (см. 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});

его даже можно использовать для создания связанных списков ...

Эту возможность обычно называют «составными литералами». Анонимные (или безымянные) структуры обозначают вложенные структуры, у которых нет имен членов.

calandoa 22.06.2009 15:47

согласно моему GCC, «ISO C90 запрещает составные литералы».

jmtd 22.06.2009 19:55

«ISO C99 поддерживает составные литералы». «В качестве расширения GCC поддерживает составные литералы в режиме C89 и в C++» (dixit info gcc). Кроме того, «Как расширение GNU, GCC позволяет инициализировать объекты со статической продолжительностью хранения с помощью составных литералов (что невозможно в ISO C99, потому что инициализатор не является константой)».

PypeBros 03.02.2011 20:31
Ответ принят как подходящий

Указатели на функции. Вы можете использовать таблицу указателей на функции для реализации, например, быстрых интерпретаторов непрямого кода (FORTH) или диспетчеров байтового кода, или для моделирования виртуальных методов, подобных объектно-ориентированному программированию.

Кроме того, в стандартной библиотеке есть скрытые жемчужины, такие как qsort (), bsearch (), strpbrk (), strcspn () [последние два полезны для реализации замены strtok ()].

Недостатком C является то, что подписанное арифметическое переполнение является неопределенным поведением (UB). Поэтому всякий раз, когда вы видите выражение, такое как x + y, оба подписанные int, оно может потенциально переполниться и вызвать UB.

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

Mark Baker 17.10.2008 12:38

Мне очень хорошо известно, что переполнение Почему - это UB. Это по-прежнему является ошибкой, потому что в стандарте должны быть по крайней мере предусмотрены библиотечные процедуры, которые могут проверять арифметическое переполнение (всех основных операций), не вызывая UB.

zvrba 20.01.2009 23:51

@zvrba, «библиотечные подпрограммы, которые могут проверять арифметическое переполнение (всех основных операций)», если бы вы добавили это, вы бы значительно снизили производительность для любых целочисленных арифметических операций. ===== Пример из практики Matlab специально ДОБАВЛЯЕТ функцию управления поведением целочисленного переполнения для переноса или насыщения. И он также выдает исключение всякий раз, когда происходит переполнение ==> Производительность целочисленных операций Matlab: ОЧЕНЬ МЕДЛЕННО. Мой собственный вывод: я думаю, что Matlab - убедительный пример, показывающий, почему вам не нужна проверка переполнения целых чисел.

Trevor Boyd Smith 11.06.2009 17:35

@zvrba, на мой взгляд, C был разработан ПРЕДПОЛАГАЯ, что всякий раз, когда вы выполняете целочисленную арифметику, вы, программист, проводите строгий анализ, чтобы УБЕДИТЬСЯ, что у вас есть ограниченный-вход-ограниченный-выход (причудливый способ сказать "убедитесь, что ваш ввод и вывод остаются в пределах диапазона ") !! Если вы не проводите такой тщательный анализ, тогда виноват не язык, а программист.

Trevor Boyd Smith 11.06.2009 17:38

Я сказал, что в стандарте должна быть предусмотрена поддержка библиотека для проверки арифметического переполнения. Каким образом библиотечная процедура может снизить производительность, если вы никогда ее не используете?

zvrba 12.06.2009 22:52

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

Andrew Keeton 22.06.2009 04:23

Ничего из этого вовсе не «скрыто». Стандартная библиотека, например, хорошо разрекламирована; если люди предпочитают не читать документацию, они дураки. Указатели на функции просто «продвинутые», они никоим образом не скрыты - с ними связано большинство текстов на языке.

Clifford 11.11.2009 16:52

Скрытность относительна, документация относительно абсолютна.

Anonymous Type 02.09.2010 05:08

зачем нужна замена strtok?

Aif 26.11.2010 16:57

Я никогда не использовал битовые поля, но они звучат круто для сверхнизкоуровневых вещей.

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), который представляет собой структуру битовых полей.

ComSubVie 25.09.2008 15:09

Битовые поля не переносимы - компилятор может свободно выбирать, будут ли в вашем примере для ног выделяться 3 старших бита или 3 младших бита.

zvrba 25.09.2008 18:25

Битовые поля - это пример того, где стандарт дает реализациям настолько большую свободу в том, как они реализованы, что на практике они почти бесполезны. Если вам важно, сколько бит занимает значение и как оно хранится, лучше использовать битовые маски.

Mark Bessey 17.10.2008 08:13

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

Adam Liss 26.10.2008 03:41

Ура для битовых полей! Я использую их все время.

c0m4 14.11.2008 00:40

@Adam: местоположение может иметь значение во встроенной системе (или где-либо еще), если вы зависите от положения битового поля в его байте. Использование масок устраняет двусмысленность. Аналогично для профсоюзов.

Steve Melnikoff 02.03.2009 01:51

@ComSubVie: с этой точки зрения ничего не переносимо :)

AnT 28.10.2009 13:14

Да, я действительно использовал это для задания, которое у меня было в прошлом году на уроке CS. Мы реализовали алгоритм LZW для работы с определенным ограничением памяти. Единственным вариантом было указать количество битов, которое будет использоваться для каждого поля в структуре, поскольку по умолчанию каждое поле было слишком большим.

dougvk 14.10.2010 11:45

Многосимвольные константы:

int x = 'ABCD';

Это устанавливает x в 0x41424344 (или 0x44434241, в зависимости от архитектуры).

Обновлено: Этот метод непереносим, ​​особенно если вы сериализуете int. Однако создание самодокументирующихся перечислений может быть чрезвычайно полезным. например

enum state {
    stopped = 'STOP',
    running = 'RUN!',
    waiting = 'WAIT',
};

Это значительно упрощает работу, если вы просматриваете необработанный дамп памяти и вам нужно определить значение перечисления, не просматривая его.

Я почти уверен, что это не переносная конструкция. Результат создания многосимвольной константы определяется реализацией.

Mark Bessey 17.10.2008 08:18

удалите запятую после "WAIT" на случай, если кто-то попытается это сделать.

blak3r 11.06.2009 11:11

@ blak3r - спасибо! это напоминает мне еще об одной скрытой особенности. см. здесь: stackoverflow.com/questions/132241/hidden-features-of-c/…

Ferruccio 11.06.2009 15:17

Проблема в том, что их нельзя сравнивать с реальными строками, поскольку в зависимости от порядка байтов они могут храниться в памяти как «STOP» или «POTS».

calandoa 22.06.2009 15:51

Он не предназначен для сравнения с настоящими струнами. Дело в том, что вы можете присвоить перечислениям уникальные значения, которые легко читать в дампе памяти.

Ferruccio 22.06.2009 16:20

Запятая была преднамеренной и синтаксически верной.

Ferruccio 22.06.2009 23:11

@Ferruccio - я считаю, что запятая действительна в C99, но не в C89. Это означает, что GCC, вероятно, проглотит его, но некоторые старые компиляторы (или более старые версии GCC) нет.

Chris Lutz 24.08.2009 02:24

Комментарии "непереносимых" полностью упускают из виду суть. Это все равно, что критиковать программу за использование INT_MAX только потому, что INT_MAX "непереносимый" :) Эта функция настолько портативна, насколько это необходимо. Константа с несколькими символами - чрезвычайно полезная функция, которая обеспечивает удобочитаемый способ создания уникальных целочисленных идентификаторов.

AnT 28.10.2009 13:36

@Chris Lutz - Я почти уверен, что конечная запятая идет обратно к K&R. Это описано во втором издании (1988 г.).

Ferruccio 28.10.2009 14:15

@Ferruccio: Вы, должно быть, думаете о конечной запятой в списках агрегированных инициализаторов. Что касается конечной запятой в объявлениях перечислений - это недавнее добавление, C99.

AnT 28.10.2009 20:59

Вы забыли HANG или BSOD :-)

JBRWilkinson 02.11.2009 19:30

Почему бы просто не использовать макросы с определенными числовыми флагами?

Vince 11.02.2010 03:58

@Vince: Макросы, даже для констант, загрязняют пространство имен программы. Аналогичным образом, почему бы не использовать сборку вместо C? ;)

Joe D 07.06.2010 22:23

Я тестировал и, по крайней мере, с gcc (пробовал на PowerPC и x86) многобайтовые символы не зависят от порядка байтов. Кто-то пробовал с другим компилятором или проверял стандарт ISO? Приведенный выше ответ кажется неправильным относительно порядка байтов.

kriss 29.03.2011 13:51

Однажды мне показали это в небольшом коде, и я спросил, что он делает:


hexDigit = "0123456789abcdef"[someNybble];

Еще один фаворит:


unsigned char bar[100];
unsigned char *foo = bar;
unsigned char blah = 42[foo];

Первое слишком просто. Я думаю, вы имели в виду someNybble ["0123456789abcdef"]. Второй не компилируется, пока вы не добавите *.

Windows programmer 03.10.2008 06:43

Я думаю, что первый правильный как есть: он преобразует целое число someNybble в диапазоне 0-15 в его шестнадцатеричный эквивалент.

Adam Liss 26.10.2008 03:35

Обе формы правильны для 1-го, но показанная на самом деле не сложная, просто необычная для восприятия, но очевидная для понимания.

Blaisorblade 19.01.2009 03:26

Не совсем скрытая функция, но мне это показалось вуду, когда я впервые увидел что-то вроде этого:


void callback(const char *msg, void *data)
{
    // do something with msg, e.g.
    printf("%s\n", msg);

    return;
    data = NULL;
}

Причина этой конструкции в том, что если вы скомпилируете это с -Wextra и без строки «data = NULL;» -, gcc выдаст предупреждение о неиспользуемых параметрах. Но с этой бесполезной строкой вы не получите предупреждения.

Обновлено: Я знаю, что есть другие (лучшие) способы предотвратить эти предупреждения. Мне это показалось странным, когда я впервые увидел это.

Разве вместо этого вы не получаете предупреждение о недоступности кода? Почему бы просто не закомментировать «данные» - это также удалит предупреждение о неиспользуемых параметрах.

Greg Whitfield 25.09.2008 17:15

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

quinmars 25.09.2008 17:31

Нет, подпись не меняется. Вы просто делаете это: void callback (const char msg, void * / data * /) Или это: void callback (const char * msg, void *)

Greg Whitfield 25.09.2008 18:02

С помощью gcc вы можете добавить неиспользуемый атрибут к параметрам: void callback (const char * msg, void * data __attribute __ ((unused)))

DGentry 25.09.2008 18:08

В отличие от использования непереносимого синтаксиса атрибут. Вы можете просто указать: (void) data; в функции. Я обычно ставлю его сразу после любых местных (так как они должны быть первыми в c89). Я также стараюсь создать такой макрос: #define UNUSED (x) (void) x, чтобы я мог просто написать: UNUSED (data).

Evan Teran 25.09.2008 18:20

Вы можете использовать '(void) data' где угодно до 'return'. (кажется, Эван это уже сказал)

akauppi 25.09.2008 23:24

@Greg Whitfield void callback (const char * msg, void *) {...} здесь не компилируется @all Я знаю, что есть много способов подавить подобные предупреждения

quinmars 26.09.2008 01:27

@quinmars - Какой компилятор вы используете? То, что я сказал вам, является стандартным C. Какую ошибку вы получаете?

Greg Whitfield 26.09.2008 13:06

Я использую gcc, я повторил попытку с g ++, и там действительно работает. Так что, похоже, это особенность C++.

quinmars 26.09.2008 14:35

Разве вы не можете просто использовать # неиспользуемые данные?

Brian Postow 03.11.2009 18:30

Скорее уловка компилятора 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();
     ...
}

Уловка классная ... :) Особенно с определенными вами макросами. :)

sundar - Remember Monica 22.10.2008 19:23

Мне очень нравятся назначенные инициализаторы, добавленные в C99 (и давно поддерживаемые в gcc):

#define FOO 16
#define BAR 3

myStructType_t myStuff[] = {
    [FOO] = { foo1, foo2, foo3 },
    [BAR] = { bar1, bar2, bar3 },
    ...

Инициализация массива больше не зависит от позиции. Если вы измените значения FOO или BAR, инициализация массива автоматически будет соответствовать их новому значению.

Синтаксис, который gcc поддерживает в течение долгого времени, не совпадает со стандартным синтаксисом C99.

Mark Baker 17.10.2008 12:36

В некоторых случаях также могут быть полезны автоматические переменные переменного размера. Они были добавлены в nC99 и долгое время поддерживаются в gcc.

void foo(uint32_t extraPadding) {
    uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];

В итоге вы получаете буфер в стеке с местом для заголовка протокола фиксированного размера и данных переменного размера. Вы можете получить тот же эффект с помощью alloca (), но этот синтаксис более компактен.

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

Будет ли это работать правильно, если размер байта / символа на целевой платформе не равен 8 битам? Знаю, такие случаи редки, но все же ... :)

Stephan202 26.04.2009 14:26

int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

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

#define INT16 short
#define INT32  long

И так далее. Это заставляет меня выдергивать волосы. Просто используйте долбанные стандартные целочисленные определения типов!

Я думаю, что это C99 или около того. Я не нашел портативного способа гарантировать, что они будут поблизости.

akauppi 25.09.2008 23:25

Они являются необязательной частью C99, но я не знаю поставщиков компиляторов, которые бы это не реализовали.

Ben Collins 26.09.2008 01:07

stdint.h не является обязательным в C99, но, очевидно, следование стандарту C99 для некоторых поставщиков (кашель Microsoft).

Ben Combee 22.10.2008 21:54

Microsoft Visual C++ также не следует стандарту Ada95. Это не компилятор C99. Это компилятор C++ 97. (Он не всегда соответствует этому стандарту, но несправедливо жаловаться на то, что это не то, на что он не претендует)

Pete Kirkham 01.03.2009 14:33

@Pete, если хочешь быть аналом: (1) Эта ветка не имеет ничего общего ни с каким продуктом Microsoft. (2) Эта ветка никогда не имела ничего общего с C++. (3) Не существует C++ 97.

Ben Collins 02.03.2009 00:20

Взгляните на azillionmonkeys.com/qed/pstdint.h - почти переносимый stdint.h

gnud 16.04.2009 18:16

@gnud: спасибо за подсказку, но меня беспокоит то, что в этом нет необходимости - большинство компиляторов реализуют стандартные определения типов. Единственный компилятор, который я когда-либо использовал, который не использовал, была старая версия GCC, адаптированная для разработки встроенных VxWorks (старый, например, GCC 2.7).

Ben Collins 17.04.2009 01:56

@Ben Collins: Он указывает, что он почти реализует C++ 98, но не соответствует некоторым требованиям. Кроме того, MSVC не поддерживает C99, особенно stdint.h, который является королевским PITA.

Matt Joiner 22.10.2009 15:52

@Anacrolix: Да. Я понял, на что он указывал. Вы, кажется, упускаете мою мысль: это ни к чему. Независимо от того, "поддерживает ли конкретный компилятор C99", на самом деле не имеет никакого отношения к тому, следует ли вам использовать стандартные целочисленные определения типов. Они переносимы и их легко определить, даже если ваш компилятор отстой. Если вам нужно указать определенную целочисленную ширину, тогда следует использовать стандартные определения типов всегда, всегда, независимо от того, откуда берутся определения.

Ben Collins 24.10.2009 01:59

Спасибо большое! Он получает резервную копию, когда я использую заголовки Windows, и вы получаете typedef unsigned long ULONG. Серьезно? Или введите typedef float FLOAT.

Puppy 08.05.2010 12:58

Насколько мне известно, в Visual Studio 2010 теперь есть stdint.h из-за высокого спроса на эту особенность C99. Это была королевская PITA, которой не было раньше.

Jonathan Sternberg 20.01.2011 00:12

Оператор запятой широко не используется. Конечно, им можно злоупотреблять, но он также может быть очень полезным. Это наиболее распространенное использование:

for (int i=0; i<10; i++, doSomethingElse())
{
  /* whatever */
}

Но вы можете использовать этот оператор где угодно. Наблюдать:

int j = (printf("Assigning variable j\n"), getValueFromSomewhere());

Каждый оператор оценивается, но значение выражения будет значением последнего оцененного оператора.

За 20 лет C я такого НИКОГДА не видел!

Martin Beckett 22.06.2009 18:55

В C++ его даже можно перегрузить.

Wouter Lievens 01.07.2009 14:45

может! = должен, конечно. Опасность с перегрузкой заключается в том, что встроенная функция уже применяется ко всему, включая void, поэтому никогда не потерпит неудачу при компиляции из-за отсутствия доступной перегрузки. То есть, дает программисту большую веревку.

Aaron 06.07.2009 21:45

Int внутри цикла не будет работать с C: это улучшение C++. Является ли "," той же операцией, что и для (i = 0, j = 10; i <j; j--, i ++)?

Aif 26.11.2010 17:00

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

Пример:

Если вам нужно знать, как компилятор хранит 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;

Это подпадает под «обычное использование, от которого я бы не рекомендовал». Псевдонимы типов и оптимизация не ладят. Вместо этого используйте объединения для ясности как для читателя, так и для компилятора.

ephemient 05.10.2008 04:16

Если быть точным, «не ладите» означает, что «этот код на самом деле может быть неправильно скомпилирован», потому что это неопределенное поведение в C.

Blaisorblade 19.01.2009 03:23

Мне понравились структуры переменного размера, которые вы могли сделать:

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 из объявления, чтобы использовать гибкий элемент массива.)

Jonathan Leffler 18.01.2012 05:53

Моя любимая «скрытая» функция C - использование% n в printf для обратной записи в стек. Обычно printf извлекает значения параметров из стека на основе строки формата, но% n может записать их обратно.

Ознакомьтесь с разделом 3.4.2 здесь. Может привести к множеству неприятных уязвимостей.

ссылка больше не работает, на самом деле кажется, что сам сайт не работает. Можете дать еще одну ссылку?

thequark 07.01.2011 07:39

@thequark: Любая статья об "уязвимостях строки формата" будет содержать некоторую информацию .. (например, crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf) .. Однако из-за характера поля сами сайты безопасности немного нестабильны, и трудно найти настоящие академические статьи by (с реализацией).

Sridhar Iyer 07.01.2011 10:08

gcc имеет ряд расширений для языка C, которые мне нравятся, которые можно найти здесь. Некоторые из моих любимых - атрибуты функции. Один чрезвычайно полезный пример - атрибут формата. Это можно использовать, если вы определяете пользовательскую функцию, которая принимает строку формата printf. Если вы включите этот атрибут функции, gcc проверит ваши аргументы, чтобы убедиться, что ваша строка формата и аргументы совпадают, и при необходимости будет генерировать предупреждения или ошибки.

int my_printf (void *my_object, const char *my_format, ...)
            __attribute__ ((format (printf, 2, 3)));

регистрировать переменные

Я раньше объявлял некоторые переменные с помощью ключевого слова register, чтобы ускорить процесс. Это подскажет компилятору C использовать регистр ЦП в качестве локального хранилища. Скорее всего, в этом больше нет необходимости, поскольку современные компиляторы C делают это автоматически.

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

Mark Baker 17.10.2008 12:45

Я почти уверен, что некоторые компиляторы не позволяют вам принимать адрес переменной, объявленной с помощью register. Так что это полезно для того, чтобы ваши намерения были ясны.

Zan Lynx 12.06.2009 03:00

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

Abel 01.11.2011 21:20

Учитывая комбинацию микроконтроллера и чтения вывода сборки, это все равно может быть полезно в каком-то странном случае.

XTL 17.02.2012 11:45

инициализация структуры до нуля

struct mystruct a = {0};

это обнулит все элементы конструкции.

Обнуляет ли он и весь массив?

Nate Parsons 28.12.2008 06:30

Однако он не обнуляет отступы, если они есть.

Mikeage 01.03.2009 16:49

@drhorrible: вы можете сделать int array [50] = {0}; Например. Но только при декларации (думаю.)

Skurmedel 26.04.2009 15:08

Разве это не делает что-то неопределенное, если структура содержит нецелочисленные типы (например, числа с плавающей запятой и двойные)?

Simon Nickerson 11.06.2009 15:20

@simonn, нет, он не выполняет неопределенное поведение, если структура содержит нецелочисленные типы. memset с 0 в памяти float / double будет по-прежнему равным нулю, когда вы интерпретируете float / double (float / double специально созданы таким образом).

Trevor Boyd Smith 11.06.2009 17:59

@Trevor Я думал, что эффект, который это оказывает на числа с плавающей запятой, - это «все нулевые байты», что во всех разумных случаях даст вам значение с плавающей запятой, равное 0,0, но это все еще определяется реализацией.

Andrew Keeton 22.06.2009 04:27

@Andrew: memset / calloc выполняет «все нулевые байты» (т.е. физические нули), что действительно не определено для всех типов. { 0 } гарантированно инициализирует все с соответствующими нулевыми значениями логичный. Например, гарантируется, что указатели получат свои правильные нулевые значения, даже если нулевым значением на данной платформе является 0xBAADFOOD.

AnT 28.10.2009 13:12

@AndreyT: Не могли бы вы подробнее рассказать о различиях между логическими и физическими нулями?

N 1.1 26.02.2010 12:38

@nvl: вы получаете физический ноль, когда вы просто принудительно устанавливаете всю память, занятую объектом, в состояние «все биты-ноль». Это то, что делает memset0 в качестве второго аргумента). Вы получаете нулевое значение логичный, когда инициализируете / назначаете 0 (или { 0 }) объекту в исходном коде. Эти два вида нулей не обязательно дают одинаковый результат. Как в примере с указателем. Когда вы выполняете memset для указателя, вы получаете указатель 0x0000. Но когда вы назначаете 0 указателю, вы получаете значение нулевого указателя, который на физическом уровне может быть 0xBAADF00D или чем-то еще.

AnT 26.02.2010 19:29

Другими словами, значение физический является явным битовым шаблоном, который хранится в памяти. Значение Логический - это то, как этот битовый шаблон интерпретируется на программном уровне.

AnT 26.02.2010 19:31

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

N 1.1 26.02.2010 21:57

@nvl: Ну, на практике разница часто носит чисто концептуальный характер. Но теоретически он может быть практически у любого типа. Например, double. Обычно это реализовано в соответствии со стандартом IEEE-754, в котором логический ноль и физический ноль совпадают. Но язык не требует IEEE-754. Таким образом, может случиться так, что когда вы сделаете double d = 0; (логический ноль), физически некоторые биты в памяти, занятой d, не будут равны нулю.

AnT 26.02.2010 22:17

То же самое со значениями bool для другого примера. Если вы используете bool b = false; (или, что то же самое, bool b = 0;), это не обязательно означает, что в физической памяти b будет обнулен (хотя на практике это обычно так).

AnT 26.02.2010 22:18

Понятно. Спасибо. Теперь я вспоминаю представление с плавающей запятой. Имеет смысл.

N 1.1 27.02.2010 00:10

@AndreyT: Отличные комментарии. +1

paercebal 08.05.2010 12:45

Назначение структуры - это круто. Многие люди, кажется, не понимают, что структуры тоже являются значениями, и их можно назначать повсюду, нет необходимости использовать 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.

Конечно, такой способ обхода больших структур влияет на производительность; это часто бывает полезно (и это действительно то, что многие люди не осознают, что вы можете это сделать), но вам нужно подумать, лучше ли передавать указатели.

Mark Baker 17.10.2008 12:47

Конечно, есть мощь. Компилятор также вполне может обнаружить использование и оптимизировать его.

unwind 17.10.2008 12:57

Будьте осторожны, если какие-либо элементы являются указателями, так как вы будете копировать сами указатели, а не их содержимое. Конечно, то же самое верно и в случае использования memcpy ().

Adam Liss 26.10.2008 03:27

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

Blaisorblade 19.01.2009 03:20

Вероятно, стоит отметить, что в C++ стандарт специально позволяет оптимизировать копию (стандарт должен позволять компиляторам реализовать его, потому что это означает, что конструктор копирования, который может иметь побочные эффекты, не может быть вызван), и поскольку большинство компиляторов C++ также являются компиляторами C, есть большая вероятность, что ваш компилятор выполняет эту оптимизацию.

Joseph Garvin 21.02.2010 20:05

Я исправил последний вызов «конструктора», он вызывал Point (0, 0), что, конечно же, неправильно, point_new () - это то, что нужно. Ой.

unwind 25.10.2010 12:33

Инициализация структуры C99 предназначена для оптимизации, в то время как этот код полагается на тот факт, что компилятор МОЖЕТ встроить функцию И МОЖЕТ отбросить копию.

dolmen 29.03.2011 01:26

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

Утверждения времени компиляции, как уже обсуждалось здесь.

//--- 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.

Blaisorblade 19.01.2009 03:24

Отрывок:

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.

http://www.gowrikumar.com/c/index.html

При инициализации массивов или перечислений вы можете поставить запятую после последнего элемента в списке инициализатора. например:

int x[] = { 1, 2, 3, };

enum foo { bar, baz, boom, };

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

Это также важно в среде с несколькими разработчиками, где, например, Эрик добавляет «baz», а затем Джордж добавляет «бум». Если Эрик решит вытащить свой код для следующей сборки проекта, он все равно будет компилироваться с изменениями Джорджа. Очень важно для управления исходным кодом нескольких ветвей и перекрывающихся графиков разработки.

Harold Bamford 01.07.2009 01:50

Перечисления могут быть C99. Инициализаторы массивов и конечная запятая - K&R.

Ferruccio 24.11.2009 14:27

Простые перечисления были в c89, AFAIK. По крайней мере, они существуют уже много лет.

XTL 17.02.2012 11:48

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 22.06.2009 19:43

@jmtd, Верно. На практике его вполне достаточно «портативного», чтобы доставить вам неприятности. Смещение любого члена, кроме первого, является поведением, определяемым реализацией, и не обязательно должно иметь такую ​​же эффективную упаковку, как массив этого типа. На практике вполне вероятно, что он имеет ту же упаковку, что и массив, поэтому этот код будет работать до тех пор, пока он не будет перенесен на платформу следующий, где он загадочным образом выйдет из строя. То же самое произошло с обычной реализацией MD5 при переносе на 64-битную версию: она скомпилировалась и запустилась, но получила другой ответ.

RBerteig 01.08.2010 04:11

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] из размера вашей структуры.

Patrick Schlüter 27.11.2010 15:48

Разве доступ к b [] не превышает объявленного размера undefined, независимо от того, существует ли для него выделенное пространство? Я бы подумал, что было бы чище использовать char b [MAX_ARRAY_SIZE], а затем вычесть MAX_ARRAY_SIZE из распределения. Еще лучше было бы, если бы массивы нулевого размера были разрешены в первую очередь, и компиляторы должны были бы рассматривать их как указатель на место начала массива, но без ограничения размера.

supercat 26.08.2011 01:31

Вот три хороших варианта в gcc:

__FILE__ 
__FUNCTION__
__LINE__
ФАЙЛ and ЛИНИЯ are standard ; C99 brings func
philant 29.11.2009 19:48

Оберните 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 определен.

fortran 28.10.2009 14:24

вам нужна запятая в макросах malloc, а не точка с запятой (по причинам, описанным @fortran). Однако это игнорирует возвращаемое значение (но опять же, я не уверен, почему эти макросы желательны).

Michael 06.01.2010 01:49

Я только что прочитал этот статья. Он имеет некоторые «скрытые функции» C и некоторых других языков.

О боже! все они вносят свой вклад в stackoverflow, извините (я здесь новичок и не заметил, что есть раздел скрытых функций) ... В любом случае, он может работать как справочник и краткое руководство по этим темам.

Rigo Vides 22.06.2009 10:34

Объектно-ориентированные макросы 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 (), ну, это макрос, но все равно приятно.

kriss 08.05.2010 12:25

А если хотите: #define countof(array) (sizeof (array) / sizeof (array[0]));)

Joe D 10.05.2010 23:53

(скрытая) особенность, которая "шокировала" меня, когда я впервые увидел, связана с 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.

Cacho Santa 28.01.2012 00:15

Используйте NaN для связанных вычислений / возврата ошибок:

// # включаем <stdint.h>
статический uint64_t iNaN = 0xFFF8000000000000;
константный двойной NaN = * (двойной *) & iNaN; // тихий NaN

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

примечание: тестирование на NaN сложно, так как NaN! = NaN ... используйте isnan (x) или сверните свой собственный. x! = x математически правильно, если x равно NaN, но имеет тенденцию быть оптимизированным некоторыми компиляторами

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

ulidtko 26.05.2011 06:27

Я обнаружил это только после 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])

Patrick Schlüter 27.11.2010 15:52

Мне нравится метод перечисления. Подход, который я использовал раньше, использовал преимущество исключения мертвого кода: «if (something_bad) {void BLORG_IS_WOOZLED (void); BLORG_IS_WOOZLED ();}», который не приводил к ошибкам до времени компоновки, хотя он давал преимущество, позволяя программист узнает через сообщение об ошибке, что blorg был взорван.

supercat 26.08.2011 01:28

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);

Обратите внимание, что обычное ужасное решение:

  • в три раза дольше
  • гораздо труднее читать
  • намного медленнее
  • менее мощный при установке на произвольный предел размера буфера (но вам придется использовать еще более длинный код, чтобы избежать этого без конкатенации константных строк).
  • использовать больше места в стеке

Также полезно разделить строковую константу на несколько строк исходного кода без использования грязного символа `\`.

dolmen 29.03.2011 01:35

Мне нравятся __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) делает его несколько избыточным.

Mark Ransom 19.01.2012 21:44

В 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;
    }
}

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