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


141

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


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

Ответы:


62

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

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

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


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

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

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

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

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

116

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

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

77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

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

#define INT16 short
#define INT32  long

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


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

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

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

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

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

73

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

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

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

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

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


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

11
В C ++ его даже можно перегрузить.
Воутер Ливенс,

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

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

63

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

struct mystruct a = {0};

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


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

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

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

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

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

52

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

int x = 'ABCD';

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

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

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

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


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

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

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

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

3
Вы забыли «HANG» или «BSOD» :-)
JBRWilkinson,

44

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

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,

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

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

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

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

37

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


18
C поддерживает цепочку, поэтому вы можете сделать a ^ = b ^ = a ^ = b;
OJ.

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

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

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

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

37

Чередование структур, таких как Устройство Даффа :

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

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

2
@ComSubVie, познакомьтесь с моим Кулаком смерти ( en.wikipedia.org/wiki/… )
Тревор Бойд Смит,

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

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

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

33

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

#define FOO 16
#define BAR 3

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

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


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

28

C99 имеет отличную инициализацию структуры любого порядка.

struct foo{
  int x;
  int y;
  char* name;
};

void main(){
  struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}


27

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

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


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

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

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

24

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

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

24

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

#include <stdio.h>

int main() {
    int a = 3;
    float b = 6.412355;
    printf("%.*f\n",a,b);
    return 0;
}

символ * достигает этого эффекта.


24

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


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

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

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

19

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

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

16

Постоянная конкатенация строк

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

Вариант использования, который у меня есть в моем текущем коде: у меня есть #define PATH "/some/path/"в файле конфигурации (на самом деле он задается файлом makefile). Теперь я хочу построить полный путь, включая имена файлов, для открытия ресурсов. Это просто идет на:

fd = open(PATH "/file", flags);

Вместо ужасного, но очень распространенного:

char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);

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

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

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


12

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

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

enum foo { bar, baz, boom, };

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


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

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

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

12

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


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

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

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

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

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

10

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

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

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

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

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

9

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

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

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

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

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


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

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

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

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

9

При использовании 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 );
}

Этот код выведет:

Здравствуй
РОФЛ

8

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


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

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

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

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

8

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

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


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

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

8

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

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

2
+1 Аккуратно. Раньше я использовал макрос CompilerAssert от Microsoft, но и ваш тоже неплох. ( #define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1])
Патрик Шлютер

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

8

Gcc (c) имеет несколько забавных функций, которые вы можете включить, например, объявления вложенных функций и форму a?: B оператора?:, Который возвращает a, если a не ложно.


8

Я обнаружил недавно 0 битовых полей.

struct {
  int    a:3;
  int    b:2;
  int     :0;
  int    c:4;
  int    d:3;
};

что даст макет

000aaabb 0ccccddd

вместо без: 0;

0000aaab bccccddd

Поле ширины 0 указывает, что следующие битовые поля должны быть установлены на следующем атомарном объекте ( char)


7

Макросы переменных аргументов в стиле 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

6

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

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

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

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


Будет ли это работать правильно, если ширина байта / символа на целевой платформе не равна 8 битам? Я знаю, что такие случаи редки, но все же ... :)
Stephan202
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.