Який улюблений трюк програмування на С? [зачинено]


134

Наприклад, я нещодавно натрапив на це в ядрі Linux:

/ * Примушуйте помилку компіляції, якщо умова істинна * /
#define BUILD_BUG_ON (умова) ((недійсний)) розмір (char [1 - 2 * !! (умова)]))

Отже, у своєму коді, якщо у вас є якась структура, яка повинна бути, скажімо, кратне 8 байт, можливо, через деякі апаратні обмеження, ви можете зробити:

BUILD_BUG_ON ((sizeof (struct mystruct)% 8)! = 0);

і він не буде компілюватися, якщо розмір структури mystruct не є кратним 8, а якщо він кратний 8, взагалі не створюється код виконання.

Ще одна хитрість, яку я знаю, - це книга "Графічні дорогоцінні камені", яка дозволяє одному файлу заголовка оголошувати і ініціалізувати змінні в одному модулі, а в інших модулях, що використовують цей модуль, просто оголошувати їх як екстернали.

#ifdef DEFINE_MYHEADER_GLOBALS
#define GLOBAL
#define INIT (x, y) (x) = (y)
#else
#define GLOBAL екстерн
#define INIT (x, y)
#endif

GLOBAL int INIT (x, 0);
GLOBAL int somefunc (int a, int b);

З цим код, який визначає x та somefunc:

#define DEFINE_MYHEADER_GLOBALS
#include "the_above_header_file.h"

в той час як код, який використовує лише x та somefunc (), робить:

#include "the_above_header_file.h"

Таким чином, ви отримуєте один заголовок, який оголошує як екземпляри глобалів, так і прототипи функцій там, де вони потрібні, та відповідні зовнішні декларації.

Отже, які ваші улюблені трюки з програмування на C?


9
Це більше схоже на трюки попереднього процесу C.
jmucchiello

Щодо BUILD_BUG_ONмакросу, що не так із використанням #errorвсередині та #if?
Рікардо

Відповіді:


80

C99 пропонує кілька справді цікавих матеріалів, використовуючи анонімні масиви:

Видалення безглуздих змінних

{
    int yes=1;
    setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
}

стає

setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));

Передача змінної кількості аргументів

void func(type* values) {
    while(*values) {
        x = *values++;
        /* do whatever with x */
    }
}

func((type[]){val1,val2,val3,val4,0});

Статичні пов'язані списки

int main() {
    struct llist { int a; struct llist* next;};
    #define cons(x,y) (struct llist[]){{x,y}}
    struct llist *list=cons(1, cons(2, cons(3, cons(4, NULL))));
    struct llist *p = list;
    while(p != 0) {
        printf("%d\n", p->a);
        p = p->next;
    }
}

Я впевнений, що багато інших цікавих прийомів я не думав.


2
Я вважаю, що ваш перший приклад також можна записати так &(int){1}, якби ви хочете трохи зрозуміти, у чому тут ваш намір.
Лілі Баллард

67

Читаючи вихідний код Quake 2, я придумав щось подібне:

double normals[][] = {
  #include "normals.txt"
};

(більш-менш я не маю під рукою код, щоб перевірити його зараз).

З того часу перед моїми очима відкрився новий світ творчого використання препроцесора. Я більше не включаю лише заголовки, а цілі фрагменти коду час від часу (це значно покращує повторне використання) :-p

Дякую Джону Кармаку! xD


13
Ви не можете сказати каркас в потоці оптимізації, не згадуючи швидкий зворотний sqrt, який був у джерелі землетрусу. en.wikipedia.org/wiki/Fast_inverse_square_root
pg1989

Звідки він у першу чергу взяв 0x5f3759df?
RSH1

2
@RoryHarvey: З того, що я міг знайти, переглядаючи, це виглядає суто емпірично. Деякі дослідження (не пам’ятаю, де я їх бачив) показали, що це було близьким до оптимального, але не повністю оптимальним. Так само, схоже, що для 64 біт було виявлено значення, а не обчислення.
Матьє М.

50

Мені подобається використовувати = {0};ініціалізацію структур, не потребуючи виклику memset.

struct something X = {0};

Це дозволить ініціалізувати всі члени структури (або масиву) до нуля (але не будь-які байти прокладки - використовуйте memset, якщо вам також потрібно нулювати їх).

Але вам слід пам’ятати, що для великих, динамічно розподілених структур є деякі проблеми .


До речі, не потрібні для глобальних змінних.
страгер

5
Не потрібен для статичних змінних. Глобальні змінні можуть бути нульовими, але це не є вимогою.
Джеймі

4
Я інколи поширюю це на: const struct something zero_something = { 0 };і тоді я можу скинути змінну під час struct something X = zero_something;руху або частково через рутину, можу використовувати "X = zero_something;". Єдине можливе заперечення полягає в тому, що воно передбачає читання даних звідкись; У цей час "memset ()" може бути швидшим, але мені подобається чіткість призначення, і в ініціалізаторі також можна використовувати інші (нульові) значення (і memset () з подальшими перетвореннями на окремого члена може бути повільніше, ніж проста копія).
Джонатан Леффлер

45

Якщо ми говоримо про фокуси c, моїм улюбленим має бути пристрій Даффа для розгортання циклу! Я просто чекаю правильної можливості прийти до мене, щоб насправді використати це в гніві ...


4
Я використовував це один раз для отримання вимірюваного підвищення продуктивності, але в наші дні це не корисно для багатьох апаратних засобів. Завжди профіль!
Дан Олсон

6
Так, люди, які не розуміють контекст пристрою Даффа, були створені в: "читабельність коду" марна, якщо код недостатньо швидкий для роботи. Напевно, жодному з людей, котрий прихильнив тебе, ніколи не довелося кодувати жорсткий реальний час.
Роб К

1
+1, мені фактично потрібно було користуватися пристроєм Даффа кілька разів. Перший раз був цикл, який в основному просто копіював речі і робив невеликі перетворення на шляху. Це було набагато, набагато швидше, ніж простий memcpy () в цій архітектурі.
Макіс

3
Злість буде з боку ваших колег та наступників, які повинні підтримувати ваш код після вас.
Джонатан Леффлер

1
Як я вже говорив, я все ще чекаю правильної можливості - але ще ніхто мене не дратував. Я пишу С вже близько 25 років, я думаю, що вперше натрапив на пристрій Даффа на початку 90-х, і мені ще не довелося ним користуватися. Як зауважували інші, цей фокус стає все менш корисним зараз, оскільки компілятори вдосконалюються в цій оптимізації.
Джексон

42

використання __FILE__та __LINE__для налагодження

#define WHERE fprintf(stderr,"[LOG]%s:%d\n",__FILE__,__LINE__);

6
На деяких компіляторах ви також отримуєте FUNCTION .
JBRWilkinson

11
__FUNCTION__є лише псевдонімом __func__і __func__знаходиться в c99. Досить зручно. __PRETTY_FUNCTION__в C (GCC) - це ще один псевдонім для __func__, але в C ++ він отримає повний підпис функції.
sklnd

ФАЙЛ показує повний шлях назви файлу, тому я використовую базове ім'я ( ФАЙЛ )
Jeegar Patel,


28

Одного разу я з партнером переосмислили повернення, щоб знайти хитру помилку корупції.

Щось на зразок:

#define return DoSomeStackCheckStuff, return

4
Сподіваємось, що в тілі функції було # define'd і # undefine'd в кінці!
страгер

Мені це не дуже подобається - перше, що мені спадає на думку, що DoSomeStackCheckStuff прикручує пам’ять через якусь помилку, і хто читає код, не знає про переосмислення повернення і цікавиться, що / пекло / відбувається.
gilligan

8
@strager Але це зробило б це в основному марним. Вся справа в тому, щоб додати деяку кальку до кожного виклику функції. В іншому випадку ви просто додасте виклик до DoSomeStackCheckStuffфункцій, які хотіли простежити.
Clueless

1
@gilligan Я не думаю, що це та річ, яку ви залишаєте ввімкненою весь час; це здається досить зручним для роботи з налагодженням з одного разу.
sunetos

це справді працює? :) Я б написав #define return if((DoSomeStackCheckStuff) && 0) ; else return... так само божевільно, мабуть!
Паоло Бонзіні

22

Мені подобається "структура хак" за те, що має об'єкт з динамічним розміром. Цей сайт також пояснює це досить добре (хоча вони посилаються на версію C99, де ви можете написати "str []" як останній член структури). ви можете зробити рядок "об'єкт" таким чином:

struct X {
    int len;
    char str[1];
};

int n = strlen("hello world");
struct X *string = malloc(sizeof(struct X) + n);
strcpy(string->str, "hello world");
string->len = n;

тут ми виділили структуру типу X на купі, яка має розмір int (для len) плюс довжину "hello world", плюс 1 (оскільки str 1 входить до розміруof (X).

Зазвичай корисно, коли ви хочете мати "заголовок" прямо перед деякими даними змінної довжини в одному блоці.


Мені особисто легше просто malloc () та realloc () і використовувати strlen (), коли мені потрібно знайти довжину, але якщо вам потрібна програма, яка ніколи не знає довжину рядка і, ймовірно, знадобиться знайти її багато раз, це, мабуть, краща дорога.
Кріс Лутц

4
"... версія C99, де можна написати" str [] "" Я бачив масиви нульового розміру в такому контексті, як str [0]; досить часто. Я думаю, що це C99. Я знаю, що старші компілятори скаржаться на масиви нульового розміру.
smcameron

3
Мені це також подобається, проте ви повинні використовувати щось на зразок malloc (offsetof (X, str) + numbytes), інакше все піде не так через проблеми з заміною та вирівнюванням. Наприклад, розмір (Stru X) може бути 8, а не 5.
Fozi

3
@Fozi: Я фактично не думаю, що це буде проблемою. Оскільки ця версія має str[1](не str[]), 1 байт str входить до sizeof(struct X). Сюди входить будь-яке прокладка між lenта str.
Еван Теран

2
@Rusky: Як це може негативно вплинути на щось? Припустимо, після цього є "набивання" str. Добре, коли я виділяю, sizeof(struct X) + 10тоді це робить strефективно 10 - sizeof(int)(або більше, оскільки ми говорили, що там прокладки) великі. Це накладення str та будь-яке набивання після нього. Єдиний спосіб, який би мав будь-яку різницю, - якби був член, після strякого все одно розривається вся справа, гнучкі члени повинні бути останніми. Будь-яке набивання в кінці, можливо, спричинить занадто багато. Надайте конкретний приклад того, як це насправді може піти не так.
Еван Теран

17

Об'єктно-орієнтований код з C, шляхом емуляції класів.

Просто створіть структуру та набір функцій, які беруть вказівник на цю структуру як перший параметр.


2
Чи є ще щось, що перекладає C ++ на C, як раніше cfront?
MarkJ

11
Це навряд чи об’єктна орієнтація. Для OO з успадкуванням вам потрібно буде додати в структуру об'єкта якусь таблицю віртуальних функцій, яка може бути перевантажена "підкласами". Для цього є багато напівфабрикатів «С з класами», але я рекомендую не залишатись поза нею.
exDM69

Це потрібно було сказати. +1 для цього.
Аміт S

3
@ exDM69, орієнтація на об'єкт - це стільки ж спосіб мислення про проблему, скільки парадигма кодування; ви можете це зробити успішно без спадкування. Я зробив це на кількох проектах, перш ніж стрибнути з повним проходом у C ++.
Марк Викуп

16

Замість

printf("counter=%d\n",counter);

Використовуйте

#define print_dec(var)  printf("%s=%d\n",#var,var);
print_dec(counter);

14

Використання дурного макро-трюку, щоб полегшити визначення записів.

#define COLUMNS(S,E) [(E) - (S) + 1]

typedef struct
{
    char studentNumber COLUMNS( 1,  9);
    char firstName     COLUMNS(10, 30);
    char lastName      COLUMNS(31, 51);

} StudentRecord;

11

Для створення змінної, яка доступна лише для читання у всіх модулях, крім тієї, про яку заявлено:

// Header1.h:

#ifndef SOURCE1_C
   extern const int MyVar;
#endif

// Source1.c:

#define SOURCE1_C
#include Header1.h // MyVar isn't seen in the header

int MyVar; // Declared in this file, and is writeable

// Source2.c

#include Header1.h // MyVar is seen as a constant, declared elsewhere

Це відчуває себе небезпечно. Це декларація та визначення, які не відповідають. Під час компіляції Source2.cкомпілятор може припустити, що MyVarце не змінюється навіть під час виклику функції на Source1.c. (Зверніть увагу, що ця, як фактична змінна const, відрізняється від вказівника на const. В останньому випадку об'єкт, що вказує на об'єкт, все ще може бути змінений через інший покажчик.)
jilles

1
Це не дає змінної, яка зчитується лише лише в деяких підрозділах компіляції. Це призводить до невизначеної поведінки (див. П. 6.2.7.2 ISO 9899, ​​а також п. 6.7.3.5).
Алесь Хакл

8

Бітові зсуви визначаються лише до суми зсуву 31 (на 32-бітове ціле число).

Що ви робите, якщо хочете мати обчислений зсув, який також повинен працювати з більш високими значеннями зрушення? Ось як це робить відео-кодек Theora:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  return (a>>(v>>1))>>((v+1)>>1);
}

Або набагато легше читати:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  unsigned int halfshift = v>>1;
  unsigned int otherhalf = (v+1)>>1;

  return (a >> halfshift) >> otherhalf; 
}

Виконання завдання способом, показаним вище, набагато швидше, ніж використання такої гілки:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  if (v<=31)
    return a>>v;
  else
    return 0;
}

... і gcc насправді це наголошує :) +1
Tim Tim

2
На моїй машині gcc-4.3.2 позбавляється гілки у другій, використовуючи інструкцію cmov (умовний хід)
Адам Розенфілд

3
"набагато швидше, ніж використання гілки": відмінність полягає в тому, що гілка є правильною для всіх значень v, тоді як halfshiftтрюк лише подвоює допустимий діапазон до 63 для 32-розрядної архітектури та 127 для 64-бітної.
Паскаль Куок

8

Оголошення масиву вказівника на функції для реалізації машин кінцевого стану.

int (* fsm[])(void) = { ... }

Найприємніша перевага полягає в тому, що просто примусити кожен стимул / стан перевірити всі кодові шляхи.

У вбудованій системі я часто малюю ІРС, щоб вказати на таку таблицю і розкривати її за потребою (за межами ISR).


Один із способів, який мені подобається в цьому, полягає в тому, що якщо у вас є функція, яка вимагає ініціалізації, ви ініціалізуєте покажчик із викликом до процедури ініціалізації. Коли це запускається, останнє, що він робить, - це замінити вказівник вказівником на фактичну функцію, а потім викликати цю функцію. Таким чином, ініціалізатор автоматично викликається при першому виклику функції, а реальна функція викликається кожного наступного разу.
TMN

7

Ще одна приємна "хитрість" перед процесором - використовувати символ "#" для друку виразів налагодження. Наприклад:

#define MY_ASSERT(cond) \
  do { \
    if( !(cond) ) { \
      printf("MY_ASSERT(%s) failed\n", #cond); \
      exit(-1); \
    } \
  } while( 0 )

редагувати: код нижче працює лише на C ++. Завдяки smcameron та Евану Терану.

Так, ствердження часу компіляції завжди чудове. Він також може бути записаний як

#define COMPILE_ASSERT(cond)\
     typedef char __compile_time_assert[ (cond) ? 0 : -1]

Макрос COMPILE_ASSERT не можна використовувати двічі, оскільки він забруднює простір імен typedef, а 2-е використання отримує: error: redefinition of typedef '__compile_time_assert'
smcameron

Ви насправді спробували це? Ви можете "набрати foo;" стільки разів, скільки вам подобається. Ось так ви робите попередні визначення. Я використовував його вже 2,5 року на кількох компіляторах, як gcc, VC, так і компіляторі для вбудованого середовища, і жодного разу не виникав жодних труднощів.
Гілад Наор

Я ненавиджу препроцесора C ... :(
hasen

1
Так, я спробував це. Я вирізав і вставив повідомлення про помилку з компілятора, який був gcc.
smcameron

1
@Gilad: законно в c ++ мати надлишкові typedefs, але не c.
Еван Теран

6

Я б насправді не назвав це улюбленим трюком, оскільки я ніколи його не використовував, але згадка про пристрої Даффа нагадала мені цю статтю про реалізацію Коротингів у C. Це завжди дає мені посміхнутися, але я впевнений, що міг би бути корисним деякий час.


Я фактично використовував цю методику на практиці, щоб зробити код, керуючий послідовністю залежної асинхронної вводу-виводу, нечітко читаною людиною. Основна відмінність полягає в тому, що я не зберігаю стан кореневища у staticзмінній, а натомість динамічно виділяю структуру та передаю вказівник на цю функцію кореневої програми. Купа макросів робить це більш приємним. Це не приємно, але краще, ніж версія async / callback, яка стрибає всюди. Я б хотів використовувати зелені нитки (через swapcontext()* nixes), якби міг.
pmdj

6
#if TESTMODE == 1    
    debug=1;
    while(0);     // Get attention
#endif

Час (0); це не впливає на програму, але компілятор видасть попередження про "це нічого не робить", цього достатньо, щоб я перейшов подивитися на рядок образи, а потім побачити справжню причину, на яку я хотів звернути на це увагу.


9
ти не міг замість цього використовувати #warning?
Стефано Борині

Мабуть, я міг. Це не зовсім стандартно, але він працював у компіляторах, які я використовую. Цікаво, що вбудований компілятор переклав #define, тоді як gcc - ні.
gbarry

6

Я прихильник xor хаків:

Зміна 2 покажчиків без третього покажчика темпу:

int * a;
int * b;
a ^= b;
b ^= a;
a ^= b;

Або мені дуже подобається список, пов'язаний з xor, лише з одним вказівником. (http://en.wikipedia.org/wiki/XOR_linked_list)

Кожен вузол пов'язаного списку - це Xor попереднього вузла та наступного вузла. Щоб перейти вперед, адресу вузлів знаходять таким чином:

LLNode * first = head;
LLNode * second = first.linked_nodes;
LLNode * third = second.linked_nodes ^ first;
LLNode * fourth = third.linked_nodes ^ second;

тощо.

або переходити назад:

LLNode * last = tail;
LLNode * second_to_last = last.linked_nodes;
LLNode * third_to_last = second_to_last.linked_nodes ^ last;
LLNode * fourth_to_last = third_to_last.linked_nodes ^ second_to_last;

тощо.

Хоча це не дуже корисно (ви не можете почати рух з довільного вузла), я вважаю це дуже крутим.


5

Ця книга походить з книги "Досить мотузки, щоб стріляти собі в ногу":

У заголовку оголосити

#ifndef RELEASE
#  define D(x) do { x; } while (0)
#else
#  define D(x)
#endif

У своєму заяві про тестування коду наприклад:

D(printf("Test statement\n"));

Функція do / while допомагає у випадку, якщо вміст макросу розшириться на кілька операторів.

Виписка буде надрукована лише в тому випадку, якщо для компілятора не використовується прапор '-D RELEASE'.

Ви можете, наприклад, передайте прапор у ваш makefile тощо.

Не впевнений, як це працює у Windows, але в * nix це добре працює


Ви можете розширити D (x) до {}, коли визначено RELEASE, так що він буде добре узгоджуватися з операторами if. В іншому випадку "if (a) D (x);" буде розгорнуто до просто "if (a)", коли Ви визначили ПОВІДОМЛЕННЯ. Це дасть вам кілька приємних помилок у
звільненні

3
@MarkJ: НІ. Так воно і є, "якщо (a) D (x);" розширюється до "if (a);" що цілком чудово. Якщо у вас було D (x) розгорнутись до {}, то "if (a) if (b) D (x); else foo ();" НЕПРАВИЛЬНО розшириться на "if (a) if (b) {}; else foo ();", в результаті чого "else foo ()" збігається з другим, якщо замість першого, якщо.
Адам Розенфілд

Якщо чесно, я в основному використовую цей макрос для тестування тверджень про друк, або якби у мене був умовний вислів, я би додав це все, наприклад. D (якщо (а) foo (););
Саймон Уокер

1
@AdamRosenfield: Використовуючи #define D(x) do { } while(0)натомість обробляє цей випадок (і його можна застосувати до гілки, яка вставляє xтакож для консистенції)
rpetrich

3

Расті фактично створив цілий набір умовних умов побудови в мові , перевірте модуль затвердження збірки:

#include <stddef.h>
#include <ccan/build_assert/build_assert.h>

struct foo {
        char string[5];
        int x;
};

char *foo_string(struct foo *foo)
{
        // This trick requires that the string be first in the structure
        BUILD_ASSERT(offsetof(struct foo, string) == 0);
        return (char *)foo;
}

У фактичному заголовку є багато інших корисних макросів, які легко встати на місце.

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


Так, я нещодавно зіткнувся з мовою ccan і розглядав можливість ввести якийсь код, але ще не обернув голову навколо "модного шляху". Дякую за посилання, однак, більше мотивації заглянути до канку, який, я дуже сподіваюся, отримає деяку тягу.
smcameron

Ну, я б не надто стурбований "дорогою мовою" до тих пір, поки його більш встановлений ... прямо зараз не можна запропонувати як проект GSOC. Це невелика і досить привітна група .. і чудове місце для скидання фрагментів :)
Tim Post

До речі, я помітив, що BuILD_ASSERT Расті просто як макрос з ядра Linux (не дивно), але йому не вистачає одного з "nots" (або чубків, або!), І помічаючи це, я думаю, що мій приклад використання макросу, який я опублікував, - це неправильно. Повинні були бути: "BUILD_BUG_ON ((sizeof (struct mystruct)% 8))"
smcameron

3

Дві хороші книги-джерела для подібних матеріалів - це Практика програмування та написання суцільного коду . Один з них (я не пам'ятаю, який) говорить: Віддавайте перевагу enum #define, де можна, тому що перелік перевіряється компілятором.


1
AFAIK, в C89 / 90 немає типової перевірки перерахунків. перерахунки просто якось зручніші #defines.
cschol

Знизу сторінка 39, 2-е ED K&R. Принаймні є можливість перевірки.
Джонатан Ватмоф

3

Не характерно для C, але мені завжди подобався оператор XOR. Одне цікаве, що він може зробити - це "підміняти без тимчасового значення":

int a = 1;
int b = 2;

printf("a = %d, b = %d\n", a, b);

a ^= b;
b ^= a;
a ^= b;

printf("a = %d, b = %d\n", a, b);

Результат:

a = 1, b = 2

a = 2, b = 1


a = 1; b = 2; a = a + b; b = ab; a = ab; дає такий же результат
Грамбот

Це також змінить a і b: a ^ = b ^ = a ^ = b;
vikhyat

@TheCapn: додаток може переповнитися.
Майкл Фукаракіс


2

Мені подобається концепція, що container_ofвикористовується, наприклад, у списках. В основному, вам не потрібно вказувати nextта lastполя для кожної структури, яка буде у списку. Натомість ви додаєте заголовок структури списку до фактично пов’язаних елементів.

Погляньте на include/linux/list.hприклади реального життя.


1

Я думаю , що використання USERDATA покажчиків досить акуратно. Сьогодні мода втрачає позиції. Це не стільки функція C, але досить зручна у використанні в C.


1
Я хотів би, щоб я зрозумів, що ти тут маєш на увазі. Чи можете ви пояснити більше? Що таке покажчик даних про користувача?
Зан Лінкс

1
Plz подивитися тут stackoverflow.com/questions/602826 / ...
epatel

це насамперед для зворотних дзвінків. Це деякі дані, які ви хочете повернути вам кожного разу при запуску зворотного дзвінка. Особливо корисно для передачі C ++ цього вказівника до зворотного дзвінка, щоб ви могли прив'язати об'єкт до події.
Еван Теран

Ага, так. Дякую. Я цим багато користуюся, але ніколи цього не називав.
Зан Рись

1

я використовую X-Macros, щоб дозволити попередньому компілятору генерувати код. Вони особливо корисні для визначення значень помилок і пов'язаних з ними рядків помилок в одному місці, але вони можуть вийти далеко за рамки цього.


1

У нашій кодовій базі є хитрість, схожа на

#ifdef DEBUG

#define my_malloc(amt) my_malloc_debug(amt, __FILE__, __LINE__)
void * my_malloc_debug(int amt, char* file, int line)
#else
void * my_malloc(int amt)
#endif
{
    //remember file and line no. for this malloc in debug mode
}

що дозволяє відслідковувати витоки пам'яті в режимі налагодження. Я завжди думав, що це круто.


1

Розваги макросами:

#define SOME_ENUMS(F) \
    F(ZERO, zero) \
    F(ONE, one) \
    F(TWO, two)

/* Now define the constant values.  See how succinct this is. */

enum Constants {
#define DEFINE_ENUM(A, B) A,
    SOME_ENUMS(DEFINE_ENUMS)
#undef DEFINE_ENUM
};

/* Now a function to return the name of an enum: */

const char *ToString(int c) {
    switch (c) {
    default: return NULL; /* Or whatever. */
#define CASE_MACRO(A, B) case A: return #b;
     SOME_ENUMS(CASE_MACRO)
#undef CASE_MACRO
     }
}

0

Ось приклад, як зробити код C абсолютно невідомим про те, що насправді використовується HW для запуску програми. Main.c робить налаштування, і тоді вільний шар може бути реалізований у будь-якому компіляторі / арці. Я думаю, що це досить акуратно, щоб трохи абстрагувати код C, тому не виходить бути специфічним.

Додавання тут повного прикладу компіляції.

/* free.h */
#ifndef _FREE_H_
#define _FREE_H_
#include <stdio.h>
#include <string.h>
typedef unsigned char ubyte;

typedef void (*F_ParameterlessFunction)() ;
typedef void (*F_CommandFunction)(ubyte byte) ;

void F_SetupLowerLayer (
F_ParameterlessFunction initRequest,
F_CommandFunction sending_command,
F_CommandFunction *receiving_command);
#endif

/* free.c */
static F_ParameterlessFunction Init_Lower_Layer = NULL;
static F_CommandFunction Send_Command = NULL;
static ubyte init = 0;
void recieve_value(ubyte my_input)
{
    if(init == 0)
    {
        Init_Lower_Layer();
        init = 1;
    }
    printf("Receiving 0x%02x\n",my_input);
    Send_Command(++my_input);
}

void F_SetupLowerLayer (
    F_ParameterlessFunction initRequest,
    F_CommandFunction sending_command,
    F_CommandFunction *receiving_command)
{
    Init_Lower_Layer = initRequest;
    Send_Command = sending_command;
    *receiving_command = &recieve_value;
}

/* main.c */
int my_hw_do_init()
{
    printf("Doing HW init\n");
    return 0;
}
int my_hw_do_sending(ubyte send_this)
{
    printf("doing HW sending 0x%02x\n",send_this);
    return 0;
}
F_CommandFunction my_hw_send_to_read = NULL;

int main (void)
{
    ubyte rx = 0x40;
    F_SetupLowerLayer(my_hw_do_init,my_hw_do_sending,&my_hw_send_to_read);

    my_hw_send_to_read(rx);
    getchar();
    return 0;
}

4
Хочете розробити, можливо, пояснити практичне використання?
Леонардо Еррера

Як wxample, якщо мені доведеться написати програму тестування, використовуючи som HW інтерфейс, який генерує переривання в підсумку. Тоді цей модуль може бути налаштований на виконання функції поза нормальною сферою дії як обробник сигналу / переривання.
eaanon01

0
if(---------)  
printf("hello");  
else   
printf("hi");

Заповніть заготовки, щоб ні привіт, ні привіт не з’явились у виході.
ans:fclose(stdout)


ви можете відформатувати код за допомогою {}кнопки на панелі інструментів (я це зробив для вас). Кнопка "Цитувати" не зберігає пробілів або не застосовує підсвічування синтаксису.
Альваро Гонсалес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.