Я знаю, що у всіх реалізаціях компілятора С є стандарт, тому не повинно бути прихованих функцій. Незважаючи на це, я впевнений, що всі розробники C мають приховані / секретні хитрощі, якими вони користуються весь час.
Я знаю, що у всіх реалізаціях компілятора С є стандарт, тому не повинно бути прихованих функцій. Незважаючи на це, я впевнений, що всі розробники C мають приховані / секретні хитрощі, якими вони користуються весь час.
Відповіді:
Функціональні покажчики. Ви можете використовувати таблицю покажчиків функцій для реалізації, наприклад, швидких інтерпретаторів коду з непрямими потоками (FORTH) або диспетчерів байт-коду, або для імітації OO-подібних віртуальних методів.
Потім у стандартній бібліотеці є приховані дорогоцінні камені, такі як qsort (), bsearch (), strpbrk (), strcspn () [останні два корисні для впровадження заміни strtok ()].
Неправильною характеристикою C є те, що підписане арифметичне переповнення є невизначеним поведінкою (UB). Отже, коли ви бачите такий вираз, як x + y, обидва підписані входами, він може потенційно переповнювати і викликати UB.
Більше хитрості компілятора 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();
...
}
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t
Це необов’язковий елемент у стандарті, але це має бути прихованою особливістю, оскільки люди постійно переробляють їх. В одній базі коду, над якою я працював (і дотепер це роблю, є багато разів), все з різними ідентифікаторами. Більшість часу це з макросами препроцесора:
#define INT16 short
#define INT32 long
І так далі. Це змушує мене витягнути волосся. Просто використовуйте чудернацькі стандартні цілі типиdedefs!
Оператор з комами широко не використовується. Це, звичайно, можна зловживати, але це також може бути дуже корисно. Це використання є найбільш поширеним:
for (int i=0; i<10; i++, doSomethingElse())
{
/* whatever */
}
Але ви можете користуватися цим оператором де завгодно. Дотримуйтесь:
int j = (printf("Assigning variable j\n"), getValueFromSomewhere());
Кожне висловлення оцінюється, але значення виразу буде значення останнього оціненого твердження.
ініціалізація структури до нуля
struct mystruct a = {0};
це зведе до нуля всі елементи структури.
memset
/ calloc
do "всі байти нульові" (тобто фізичні нулі), що дійсно не визначено для всіх типів. { 0 }
гарантовано вселяє все правильними логічними нульовими значеннями. Наприклад, покажчики гарантуються для отримання належних нульових значень, навіть якщо нульове значення на даній платформі 0xBAADFOOD
.
memset
робить ( 0
як другий аргумент). Ви отримуєте логічний нуль, коли ініціалізуєте / призначите 0
(або { 0 }
) об'єкт у вихідному коді. Ці два нулі не обов'язково дають однаковий результат. Як у прикладі з вказівником. Виконуючи memset
вказівник, ви отримуєте 0x0000
вказівник. Але при призначенні 0
вказівника ви отримуєте нульове значення вказівника , яке на фізичному рівні може бути 0xBAADF00D
чи будь-що інше.
double
. Зазвичай він реалізується відповідно до стандарту IEEE-754, в якому логічний нуль і фізичний нуль однакові. Але IEEE-754 мовою не потрібен. Тож може статися так, що коли ви зробите double d = 0;
(логічний нуль), фізично деякі біти в пам'яті, зайняті, d
не будуть нульовими.
Багатозначні константи:
int x = 'ABCD';
Це встановлюється x
на 0x41424344
(або 0x44434241
, залежно від архітектури).
EDIT: Ця методика не є портативною, особливо якщо ви серіалізуєте int. Однак це може бути надзвичайно корисно для створення переписок самодокументації. напр
enum state {
stopped = 'STOP',
running = 'RUN!',
waiting = 'WAIT',
};
Це набагато простіше, якщо ви дивитесь на неочищений дамп пам'яті і вам потрібно визначити значення перерахунку, не потребуючи його шукати.
Я ніколи не використовував бітові поля, але вони звучать круто для наднизьких рівнів.
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)
.
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
Взагалі, однак, мені не подобаються хитромудрі хитрощі, але роблять код надмірно складним для читання (як приклад swap), і я люблю ті, які роблять код яснішим і безпосередньо передають наміри (як, наприклад, FSM) .
Переплетення таких структур, як пристрій Даффа :
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);
}
}
Мені дуже подобаються призначені ініціалізатори, додані в C99 (і підтримуються в gcc протягом тривалого часу):
#define FOO 16
#define BAR 3
myStructType_t myStuff[] = {
[FOO] = { foo1, foo2, foo3 },
[BAR] = { bar1, bar2, bar3 },
...
Ініціалізація масиву вже не залежить від позиції. Якщо змінити значення FOO або BAR, ініціалізація масиву автоматично відповідатиме їх новому значенню.
анонімні структури та масиви - це моя улюблена. (пор .: http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html )
setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));
або
void myFunction(type* values) {
while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});
він навіть може бути використаний для інстанціювання пов'язаних списків ...
gcc має низку розширень до мови С, що мені подобається, які можна знайти тут . Деякі з моїх улюблених є атрибутами функції . Один надзвичайно корисний приклад - атрибут формату. Це можна використати, якщо ви визначите власну функцію, яка приймає рядок формату printf. Якщо ви ввімкнете цей атрибут функції, gcc здійснить перевірку ваших аргументів, щоб гарантувати, що ваш рядок і аргументи формату збігаються, а також генеруватиме попередження чи помилки у відповідних випадках.
int my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 2, 3)));
(прихована) функція, яка "шокувала" мене, коли я вперше побачила, стосується printf. ця функція дозволяє використовувати змінні для форматування самих специфікаторів формату. шукайте код, ви побачите краще:
#include <stdio.h>
int main() {
int a = 3;
float b = 6.412355;
printf("%.*f\n",a,b);
return 0;
}
персонаж * досягає цього ефекту.
Ну ... Я думаю, що одним із сильних моментів мови C є її портативність та стандартність, тому щоразу, коли я знаходжу якийсь "прихований трюк" у виконанні, яким я зараз користуюся, я намагаюся не використовувати його, тому що намагаюся зберегти свою Код С максимально стандартний і портативний.
Твердження, складені часом, про які вже говорилося тут .
//--- 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);
Постійне з'єднання рядків
Я був дуже здивований, не побачивши це все у відповідях, оскільки всі компілятори, які я знаю, підтримують його, але, здається, багато програмістів це ігнорують. Іноді це справді зручно і не тільки при написанні макросів.
У моєму поточному коді є випадок використання: у мене є #define PATH "/some/path/"
файл конфігурації (він дійсно задається makefile). Тепер я хочу побудувати повний шлях, включаючи назви файлів, щоб відкрити ресурси. Це просто переходить до:
fd = open(PATH "/file", flags);
Замість жахливих, але дуже поширених:
char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);
Зауважте, що загальне жахливе рішення:
Ну, я ніколи його не використовував, і не впевнений, чи рекомендував би я колись комусь, але я вважаю, що це питання було б незавершеним без згадки про спільну рутинну хитрість Саймона Татама .
Під час ініціалізації масивів або переліків ви можете поставити кому після останнього елемента у списку ініціалізатора. наприклад:
int x[] = { 1, 2, 3, };
enum foo { bar, baz, boom, };
Це було зроблено так, що якщо ви автоматично генеруєте код, вам не потрібно буде турбуватися про усунення останньої коми.
Призначення структури - це класно. Багато людей, здається, не усвідомлюють, що структури теж є значеннями, і їх можна призначати навколо, не потрібно використовувати 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);
Таким чином ви можете написати досить чистий і об'єктно-орієнтований код-ish, все в звичайному стандартному С.
Дивна індексація:
int v[100]; int index = 10;
/* v[index] it's the same thing as index[v] */
Компілятори C реалізують один із декількох стандартів. Однак наявність стандарту не означає, що всі аспекти мови визначені. Наприклад, пристрій Даффа - це улюблена «прихована» функція, яка стала настільки популярною, що сучасні компілятори мають код розпізнавання спеціального призначення для того, щоб методи оптимізації не обмежували бажаний ефект цього часто використовуваного шаблону.
Загалом, приховані функції або мовні хитрощі відлякують, коли ви працюєте по краю бритви залежно від стандартних стандартів C, якими користується ваш компілятор. Багато таких хитрощів не працюють від одного компілятора до іншого, і часто такі види функцій перестають переходити від однієї версії набору компілятора даного виробника до іншої версії.
Різні хитрощі, які порушили код С, включають:
Інші проблеми та проблеми, які виникають, коли програмісти роблять припущення щодо моделей виконання, які в більшості стандартів C визначені як поведінка "залежно від компілятора".
Під час використання 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 );
}
Цей код виведе:
Привіт ROFL
використання INT (3) для встановлення точки перерви у коді є моїм улюбленим часом
Моя улюблена "прихована" функція C - це використання% n у printf для повернення до стека. Зазвичай printf виводить значення параметрів із стека на основі рядка формату, але% n може записати їх назад.
Ознайомтесь з розділом 3.4.2 тут . Може призвести до безлічі неприємних вразливих місць.
Перевірка припущення під час компіляції за допомогою enums: Дурний приклад, але може бути дуже корисним для бібліотек з константами конфігуруваного часу компіляції.
#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)
};
#define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1]
)
Gcc (c) має деякі цікаві функції, які ви можете ввімкнути, такі як вкладені декларації функції та форма a:: b оператора?:, Яка повертає a, якщо a - неправдиво.
Я нещодавно відкрив 0 бітфілів.
struct {
int a:3;
int b:2;
int :0;
int c:4;
int d:3;
};
який дасть макет
000aaabb 0ccccddd
замість без: 0;
0000aaab bccccddd
Поле ширини 0 говорить про те, що наступні бітові поля слід встановити на наступному атомному об'єкті ( char
)
Макроси змінних аргументів у стилі C99, ака
#define ERR(name, fmt, ...) fprintf(stderr, "ERROR " #name ": " fmt "\n", \
__VAR_ARGS__)
які б використовувались як
ERR(errCantOpen, "File %s cannot be opened", filename);
Тут я також використовую оператор строгізації та константа константних рядків, інші функції, які мені дуже подобаються.
Автоматичні змінні розміру розміру також корисні в деяких випадках. Вони були додані i nC99 і підтримуються в gcc протягом тривалого часу.
void foo(uint32_t extraPadding) {
uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];
Ви опиняєтесь буфером на стеці, в якому є місце для заголовка протоколу фіксованого розміру плюс даних змінного розміру. Такий же ефект можна отримати і аллока (), але цей синтаксис є більш компактним.
Ви повинні переконатися, що extraPadding є розумним значенням перед тим, як викликати цю рутину, або в кінцевому підсумку видуває стек. Вам доведеться з розумом перевірити аргументи перед тим, як викликати malloc або будь-яку іншу техніку розподілу пам’яті, тож це насправді незвично.