Скрытые особенности C
Я знаю, что есть стандарт для всех реализаций компилятора C, поэтому не должно быть скрытых функций. Несмотря на это, я уверен, что у всех разработчиков C скрытые/секретные трюки, которые они используют все время.
Ответы
Ответ 1
Указатели функций. Вы можете использовать таблицу указателей функций для реализации, например, быстрых программных интерпретаторов кода (FORTH) или байт-кода, или для имитации виртуальных методов, подобных OO.
Затем в стандартной библиотеке есть скрытые камни, такие как qsort(), bsearch(), strpbrk(), strcspn() [последние два являются полезными для реализации замены strtok()].
Недостатком C является то, что арифметическое переполнение подписей является undefined (UB). Поэтому всякий раз, когда вы видите выражение, такое как x + y, оба являются подписанными ints, он может потенциально переполняться и вызывать UB.
Ответ 2
Больше трюка компилятора 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();
...
}
Ответ 3
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t
Это дополнительный элемент в стандарте, но он должен быть скрытой функцией, потому что люди постоянно переопределяют их. Одна базовая база кода, над которой я работал (и до сих пор), имеет несколько переопределений, все с разными идентификаторами. Большую часть времени он использует макросы препроцессора:
#define INT16 short
#define INT32 long
И так далее. Это заставляет меня хотеть вытащить мои волосы. Просто используйте стандартные правила typedefs!
Ответ 4
Оператор запятой широко не используется. Это, безусловно, можно злоупотреблять, но это также может быть очень полезно. Это использование является наиболее распространенным:
for (int i=0; i<10; i++, doSomethingElse())
{
/* whatever */
}
Но вы можете использовать этот оператор в любом месте. Обратите внимание:
int j = (printf("Assigning variable j\n"), getValueFromSomewhere());
Каждый оператор оценивается, но значение выражения будет иметь значение последнего оцениваемого оператора.
Ответ 5
инициализация структуры до нуля
struct mystruct a = {0};
это приведет к нулю всех элементов структуры.
Ответ 6
Многосимвольные константы:
int x = 'ABCD';
Это устанавливает x
в 0x41424344
(или 0x44434241
, в зависимости от архитектуры).
EDIT: Этот метод не переносится, особенно если вы сериализуете int.
Однако очень удобно создавать самодокументированные перечисления. например.
enum state {
stopped = 'STOP',
running = 'RUN!',
waiting = 'WAIT',
};
Это делает его намного проще, если вы смотрите на необработанный дамп памяти и должны определить значение перечисления без необходимости искать его.
Ответ 7
Я никогда не использовал бит-поля, но они звучат круто для материалов сверхнизкого уровня.
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)
.
Включенные комментарии Aaron и leppie, спасибо ребятам.
Ответ 8
Структуры чередования, такие как Duff Device:
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);
}
}
Ответ 9
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).
Ответ 10
Я очень люблю назначенные инициализаторы, добавленные в C99 (и поддерживаемые в gcc в течение длительного времени):
#define FOO 16
#define BAR 3
myStructType_t myStuff[] = {
[FOO] = { foo1, foo2, foo3 },
[BAR] = { bar1, bar2, bar3 },
...
Инициализация массива больше не зависит от позиции. Если вы измените значения FOO или BAR, инициализация массива будет автоматически соответствовать их новому значению.
Ответ 11
C99 имеет некоторую удивительную инициализацию структуры любого порядка.
struct foo{
int x;
int y;
char* name;
};
void main(){
struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}
код >
Ответ 12
анонимные структуры и массивы - мой любимый. (см. 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});
его можно даже использовать для создания связанных списков...
Ответ 13
Ну... Я думаю, что одной из сильных сторон языка C является его мобильность и стандартность, поэтому всякий раз, когда я нахожу "скрытый трюк" в реализации, которую я использую в настоящее время, я стараюсь не использовать ее, потому что я пытаюсь чтобы мой C-код был стандартным и портативным, насколько это возможно.
Ответ 14
gcc имеет ряд расширений для языка C, который мне нравится, который можно найти здесь. Некоторые из моих любимых атрибутов . Одним из чрезвычайно полезных примеров является атрибут формата. Это можно использовать, если вы определяете пользовательскую функцию, которая принимает строку формата printf. Если вы включите этот атрибут функции, gcc проверит ваши аргументы, чтобы убедиться, что строка и аргументы формата совпадают, и при необходимости создадут предупреждения или ошибки.
int my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 2, 3)));
Ответ 15
(скрытая) функция, которая "шокировала" меня, когда я впервые увидел, касается printf. эта функция позволяет вам использовать переменные для самих спецификаторов формата форматирования. найдите код, вы увидите лучше:
#include <stdio.h>
int main() {
int a = 3;
float b = 6.412355;
printf("%.*f\n",a,b);
return 0;
}
символ * достигает этого эффекта.
Ответ 16
Утверждения времени компиляции, как уже обсуждались здесь.
//--- 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);
Ответ 17
Конкатенация константной строки
Я был очень удивлен, не увидев в ответах все это уже давно, поскольку все компиляторы, которых я знаю, поддерживают его, но многие программисты, похоже, игнорируют это. Иногда это очень удобно и не только при написании макросов.
Использовать случай, который у меня есть в моем текущем коде:
У меня есть #define PATH "/some/path/"
в файле конфигурации (на самом деле он установлен в make файле). Теперь я хочу построить полный путь, включая имена файлов, чтобы открыть ressources. Он просто идет к:
fd = open(PATH "/file", flags);
Вместо ужасного, но очень распространенного:
char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);
Обратите внимание, что общее ужасное решение:
- в три раза больше
- гораздо менее легко читать
- намного медленнее
- менее мощный в нем установлен на произвольный размер ограничения размера буфера (но вам нужно будет использовать еще более длинный код, чтобы избежать этого без контенации постоянных строк).
- использовать больше пространства стека
Ответ 18
Ну, я никогда не использовал его, и я не уверен, когда-либо рекомендую его кому-либо, но я чувствую, что этот вопрос будет неполным без упоминания Саймона Татхама совлокальный трюк.
Ответ 19
Назначение структуры круто. Многие люди, похоже, не понимают, что структуры тоже являются значениями, и их можно назначить, нет необходимости использовать memcpy()
, когда простое присваивание делает трюк.
Например, рассмотрим некоторую воображаемую двумерную графическую библиотеку, она может определить тип для представления координаты экрана (целое):
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.
Ответ 20
При инициализации массивов или перечислений вы можете поместить запятую после последнего элемента в список инициализаторов. например:
int x[] = { 1, 2, 3, };
enum foo { bar, baz, boom, };
Это было сделано, так что, если вы автоматически генерируете код, вам не нужно беспокоиться об устранении последней запятой.
Ответ 21
Странное векторное индексирование:
int v[100]; int index = 10;
/* v[index] it the same thing as index[v] */
Ответ 22
Компиляторы C реализуют один из нескольких стандартов. Однако наличие стандарта не означает, что определены все аспекты языка. устройство Duff, например, является любимой "скрытой" функцией, которая стала настолько популярной, что современные компиляторы имеют специальный код распознавания цели, чтобы гарантировать, что оптимизация методы не разрушают желаемый эффект этого часто используемого шаблона.
В общем, скрытые функции или языковые трюки не рекомендуется, так как вы работаете на бритвенном краю любого стандартного (-ов), используемого вашим компилятором. Многие из таких трюков не работают с одним компилятором на другой, и часто эти функции могут не сработать с одной версии компилятора от конкретного производителя до другой версии.
Различные трюки, которые сломали код C, включают в себя:
- Опираясь на то, как компилятор излагает структуры в памяти.
- Предположения о конъюнкции целых чисел /float.
- Предположения по функции ABI.
- Предположения относительно направления роста кадров стека.
- Предположения о порядке выполнения внутри операторов.
- Предположения о порядке выполнения операторов в аргументах функций.
- Предположения о размере бит или точности коротких, int, long, float и double типов.
Другие проблемы и проблемы, возникающие всякий раз, когда программисты делают предположения о моделях исполнения, которые указаны в большинстве стандартов C как поведение, зависящее от компилятора.
Ответ 23
При использовании 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
Ответ 24
используя INT (3), чтобы установить точку останова в коде, является моим любимым временем
Ответ 25
Моей любимой "скрытой" особенностью C является использование% n в printf для записи в стек. Обычно printf выводит значения параметров из стека на основе строки формата, но% n может записать их обратно.
Ознакомьтесь с разделом 3.4.2 здесь. Может привести к множеству неприятных уязвимостей.
Ответ 26
В Gcc (c) есть некоторые забавные функции, которые вы можете включить, такие как вложенные объявления функций и форма?? b оператора?:, которая возвращает a, если a не является ложным.
Ответ 27
Предварительная проверка времени компиляции с использованием перечислений:
Глупый пример, но может быть действительно полезен для библиотек с настраиваемыми константами времени компиляции.
#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)
};
Ответ 28
В последнее время я обнаружил 0 бит полей.
struct {
int a:3;
int b:2;
int :0;
int c:4;
int d:3;
};
который даст макет
000aaabb 0ccccddd
вместо без: 0;
0000aaab bccccddd
Поле ширины 0 указывает, что следующие последующие битовые поля должны быть установлены на следующем атомарном объекте (char
)
Ответ 29
Макросы с переменным аргументом в стиле C99, aka
#define ERR(name, fmt, ...) fprintf(stderr, "ERROR " #name ": " fmt "\n", \
__VAR_ARGS__)
который будет использоваться как
ERR(errCantOpen, "File %s cannot be opened", filename);
Здесь я также использую оператор stringize и константу строковой константы, другие функции, которые мне действительно нравятся.
Ответ 30
В некоторых случаях также полезны переменные с переменным размером. Они были добавлены я nC99 и поддерживались в gcc в течение длительного времени.
void foo(uint32_t extraPadding) {
uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];
В итоге вы получаете буфер в стеке с местом для заголовка протокола фиксированного размера плюс данные об изменении размера. Вы можете получить тот же эффект с alloca(), но этот синтаксис более компактен.
Вы должны убедиться, что extraPadding является разумным значением перед вызовом этой подпрограммы, или вы взорвали стек. Вам нужно будет проверить аргументы перед вызовом malloc или любой другой способ выделения памяти, поэтому это не очень необычно.