Какой твой любимый трюк на C? [закрыто]

134

Например, я недавно сталкивался с этим в ядре Linux:

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

Итак, в вашем коде, если у вас есть какая-то структура, которая должна иметь размер, скажем, кратный 8 байтам, возможно, из-за некоторых аппаратных ограничений, вы можете сделать следующее:

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

и он не скомпилируется, если размер struct mystruct не кратен 8, а если он кратен 8, код времени выполнения вообще не генерируется.

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

#ifdef DEFINE_MYHEADER_GLOBALS
#define GLOBAL
#define INIT (x, y) (x) = (y)
#else
#define GLOBAL extern
#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"

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

Итак, какие у вас любимые приемы программирования на С в этом направлении?

smcameron
источник
9
Это больше похоже на приемы препроцессора Си.
jmucchiello
О BUILD_BUG_ONмакросе, что не так с использованием #errorinside и #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
Вы не можете сказать carmack в потоке оптимизации, не упомянув быстрый обратный sqrt, который был в источнике Quake. en.wikipedia.org/wiki/Fast_inverse_square_root
pg1989
Откуда он взял 0x5f3759df в первую очередь?
RSH1
2
@RoryHarvey: Судя по тому, что я смог найти, это было чисто эмпирическим. Некоторые исследования (я не помню, где я их видел) показали, что это было близко к оптимальному, но не полностью оптимальному. Аналогично, кажется, что для 64 битов значение было обнаружено, а не вычислено.
Матье М.
50

Я люблю использовать = {0};для инициализации структур без необходимости вызывать memset.

struct something X = {0};

Это инициализирует все члены структуры (или массива) нулями (но не байтами заполнения - используйте memset, если вам нужно также обнулить их).

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

Джон Картер
источник
Кстати, не нужно для глобальных переменных.
Strager
5
Не требуется для статических переменных. Глобальные переменные могут быть обнулены, но это не обязательно.
Джейми
4
Я иногда расширяю это до: const struct something zero_something = { 0 };и затем я могу сбросить переменную на лету с помощью struct something X = zero_something;или частично пройдя процедуру, я могу использовать 'X = zero_something;'. Единственное возможное возражение состоит в том, что это предполагает чтение данных откуда-то; в наши дни memset () может быть быстрее, но мне нравится ясность назначения, и в инициализаторе также можно использовать значения, отличные от нуля (и memset (), за которыми следуют настройки для отдельного члена может быть медленнее, чем простая копия).
Джонатан Леффлер
45

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

Джексон
источник
4
Я использовал его один раз для получения ощутимого прироста производительности, но сейчас он бесполезен для большого количества оборудования. Всегда профиль!
Дэн Олсон
6
Да, такие люди, которые не понимают контекст, в котором было создано устройство Даффа: «читабельность кода» бесполезна, если код недостаточно быстр для работы. Вероятно, никому из тех, кто проголосовал против вас, никогда не приходилось кодировать в режиме реального времени.
Роб К
1
+1, мне действительно нужно было использовать устройство Даффа несколько раз. Первый раз был цикл, который в основном просто копировал материал и делал небольшие преобразования в пути. Это было намного, намного быстрее, чем простой memcpy () в этой архитектуре.
Макис
3
Гнев будет от ваших коллег и преемников, которые должны поддерживать ваш код после вас.
Джонатан Леффлер
1
Как я уже сказал, я все еще жду подходящей возможности - но никто еще не раздражал меня достаточно. Я пишу C около 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
FILE показывает полный путь к имени файла, поэтому я использую базовое имя ( FILE )
Jeegar Patel
31

В C99

typedef struct{
    int value;
    int otherValue;
} s;

s test = {.value = 15, .otherValue = 16};

/* or */
int a[100] = {1,2,[50]=3,4,5,[23]=6,7};
Джаспер Беккерс
источник
28

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

Что-то вроде:

#define return DoSomeStackCheckStuff, return
Andrew Barrett
источник
4
Надеюсь, это было # define'd в теле функции и # undefine'd в конце!
Strager
Мне это не очень нравится - первое, что приходит мне в голову, это то, что DoSomeStackCheckStuff испортил память из-за некоторой ошибки, и тот, кто читает код, не знает о переопределении возврата и интересуется, что происходит с / hell.
Гиллиган
8
@strager Но это сделало бы это в основном бесполезным. Весь смысл в том, чтобы добавить некоторую трассировку для каждого вызова функции. В противном случае вы просто добавите вызов к DoSomeStackCheckStuffфункциям, которые вы хотите отследить.
невежественный
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 включена в sizeof (X).

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

Эван Теран
источник
Я лично считаю, что проще просто самостоятельно выполнять malloc () и realloc () и использовать strlen () всякий раз, когда мне нужно найти длину, но если вам нужна программа, которая никогда не знает длину строки и, вероятно, найдет ее много раз, это, вероятно, лучшая дорога.
Крис Латс
4
"... версия C99, где вы можете написать" str [] "" Я видел массивы нулевого размера в таком контексте, как str [0]; довольно часто. Я думаю, что это C99. Я знаю, что старые компиляторы жалуются на массивы нулевого размера.
smcameron
3
Мне это тоже нравится, однако вы должны использовать что-то вроде malloc (offsetof (X, str) + numbytes), иначе все пойдет не так из-за проблем с дополнением и выравниванием. Например, sizeof (struct X) может быть 8, а не 5.
Fozi
3
@ Фози: Я на самом деле не думаю, что это будет проблемой. Поскольку эта версия имеет str[1](не str[]), 1 байт строки включен в 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
Это вряд ли объектная ориентация. Для ОО с наследованием вам нужно добавить в структуру вашего объекта какую-нибудь таблицу виртуальных функций, которая может быть перегружена «подклассами». Для этой цели существует множество недоделанных фреймворков в стиле "C с классами", но я рекомендую держаться подальше от этого.
exDM69
Это нужно было сказать. +1 за это.
Амит С
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;
оборота EvilTeach
источник
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;
}
Nils Pipenbrinck
источник
... и gcc на самом деле указывает на это :) +1
Tim Post
2
На моей машине gcc-4.3.2 избавляется от ветви во второй, используя инструкцию cmov (условное перемещение)
Адам Розенфилд,
3
«намного быстрее, чем использование ветвления»: разница в том, что ветвь верна для всех значений v, в то время как halfshiftуловка только удваивает допустимый диапазон до 63 в 32-разрядной архитектуре и до 127 в 64-разрядной.
Паскаль Куок
8

Объявление массива указателей на функции для реализации конечных автоматов.

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

Самое приятное преимущество заключается в том, что каждый стимул / состояние просто заставляет проверять все пути кода.

Во встроенной системе я часто сопоставляю ISR, чтобы указывать на такую ​​таблицу и обнаруживать ее по мере необходимости (за пределами 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: переопределение typedef '__compile_time_assert'
smcameron
Вы действительно пробовали это? Вы можете "typedef foo;" столько раз, сколько хотите. Вот как вы делаете предварительные объявления. Я использовал его в течение 2,5 лет на нескольких компиляторах, gcc, VC и компиляторе для встроенной среды, и никогда не сталкивался с какими-либо трудностями.
Гилад Наор
Я ненавижу препроцессор C ... :(
hasen
1
Да, я попробовал это. Я вырезал и вставил сообщение об ошибке от компилятора, который был gcc.
smcameron
1
@Gilad: в c ++ допустимо иметь избыточные определения типов, но не в c.
Эван Теран
6

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

Дэн Олсон
источник
Я фактически использовал эту технику на практике, чтобы сделать код, управляющий последовательностью зависимых асинхронных операций ввода / вывода, более читабельным для человека. Основное отличие состоит в том, что я не сохраняю состояние сопрограммы в staticпеременной, а вместо этого динамически выделяю структуру и передаю указатель на нее в функцию сопрограммы. Куча макросов делает это более приемлемым. Это не хорошо, но лучше, чем версия с асинхронным вызовом / обратным вызовом, которая скачет повсюду. Я бы использовал зеленые темы (через swapcontext()on * nixes), если бы мог.
pmdj
6
#if TESTMODE == 1    
    debug=1;
    while(0);     // Get attention
#endif

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

gbarry
источник
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;

и т.п.

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

hamiltop
источник
5

Этот из книги «Хватит веревки, чтобы выстрелить себе в ногу»:

В шапке заявляю

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

В вашем коде поместите операторы тестирования, например:

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

Функция do / while помогает в случае, если содержимое макроса расширяется до нескольких операторов.

Оператор будет напечатан только в том случае, если флаг компилятора «-D RELEASE» не используется.

Вы можете тогда, например. передать флаг вашему make-файлу и т. д.

Не уверен, как это работает в Windows, но в * nix это работает хорошо

Саймон Уокер
источник
Возможно, вы захотите расширить D (x) до {}, когда определено RELEASE, чтобы оно хорошо сочеталось с операторами if. В противном случае «if (a) D (x);» будет расширяться до "если (а)", когда вы определили RELEASE. Это даст вам несколько приятных ошибок в версии RELEASE
MarkJ
3
@MarkJ: НЕТ. Так оно и есть, «если (а) D (х);» расширяется до «если (а);» что прекрасно Если у вас есть D (x), расширяется до {}, тогда «if (a) if (b) D (x); еще foo ();» НЕПРАВИЛЬНО расширится до "if (a) if (b) {}; else foo ();", что приведет к совпадению "else foo ()" со вторым if вместо первого if.
Адам Розенфилд
Честно говоря, я в основном использую этот макрос для проверки операторов печати, или если бы у меня было условное утверждение, я бы включил все это, например. D (если (a) foo (););
Саймон Уолкер
1
@AdamRosenfield: использование #define D(x) do { } while(0)вместо этого обрабатывает этот случай (и может быть применено к ветви, которая также вставляет xдля согласованности)
rpetrich
3

Rusty на самом деле создал целый набор условных выражений сборки в ccan , проверьте модуль build assert:

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

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

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

Tim Post
источник
Да, я недавно натолкнулся на ccan и собирался добавить некоторый код, но пока не обернулся вокруг «ccan way». Спасибо за ссылку, хотя, больше мотивации для изучения ccan, что я действительно надеюсь получить некоторую тягу.
smcameron
Ну, я бы не стал слишком беспокоиться о «пути ccan», пока он не станет более устоявшимся ... сейчас ccan-lint предлагается в качестве проекта GSOC. Это небольшая и довольно дружная группа .. и отличное место, чтобы сбрасывать фрагменты :)
Tim Post
Кстати, я заметил, что Rusty BuILD_ASSERT похож на макрос из ядра linux (неудивительно), но ему не хватает одного из «неимущих» (или ударов, или!), И я заметил, что мой пример использования макроса, который я опубликовал, выглядит следующим образом: не верно. Должно было быть: «BUILD_BUG_ON ((sizeof (struct mystruct)% 8))»
smcameron
3

Двумя хорошими исходными книгами для такого рода вещей являются «Практика программирования и написания твердого кода» . Один из них (я не помню, какой именно) говорит: предпочитайте enum вместо #define, где вы можете, потому что enum проверяется компилятором.

Yuval F
источник
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);

Результат:

а = 1, б = 2

а = 2, б = 1

Карл
источник
а = 1; б = 2; а = а + б; b = ab; a = ab; тоже дает тот же результат
Грэмбот
Это также поменяет местами a и b: a ^ = b ^ = a ^ = b;
викхять
@TheCapn: дополнение может переполниться, хотя.
Майкл Фукаракис
2

Мне нравится понятие, container_ofиспользуемое, например, в списках. В принципе, вам не нужно указывать nextи lastполя для каждой структуры, которые будут в списке. Вместо этого вы добавляете заголовок структуры списка к фактическим связанным элементам.

Посмотрите на include/linux/list.hреальные примеры.

Вилиам
источник
1

Я думаю, что использование указателей userdata довольно аккуратно. Мода сдает позиции в наши дни. Это не столько функция C, но довольно простая в использовании в C.

epatel
источник
1
Я хотел бы, чтобы я понял, что вы имели в виду здесь. Не могли бы вы объяснить больше? Что такое указатель пользовательских данных?
Zan Lynx
1
Пожалуйста, смотрите здесь stackoverflow.com/questions/602826/…
epatel
это в первую очередь для обратных вызовов. Это некоторые данные, которые вы хотели бы возвращать вам при каждом вызове. Особенно полезно для передачи C ++ этого указателя в обратный вызов, чтобы вы могли связать объект с событием.
Эван Теран
О да. Спасибо. Я часто этим пользуюсь, но никогда так не называл.
Zan Lynx
1

Я использую X-Macros, чтобы позволить прекомпилятору генерировать код. Они особенно полезны для определения значений ошибок и связанных строк ошибок в одном месте, но они могут пойти гораздо дальше.

оборота JayG
источник
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
}

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

jdizzle
источник
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
     }
}
sanjoyd
источник
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;
}
eaanon01
источник
4
Хотите разработать, возможно, объясняя практическое использование?
Леонардо Эррера
В качестве примера, если мне нужно написать тестовую программу, используя сом HW интерфейс, который генерирует прерывания в конце. Затем этот модуль может быть настроен на выполнение функции вне обычной области действия в качестве обработчика сигнала / прерывания.
eaanon01
0
if(---------)  
printf("hello");  
else   
printf("hi");

Заполните пропуски, чтобы ни привет, ни привет не появлялись на выходе.
анс:fclose(stdout)

justgo
источник
Вы можете отформатировать код с помощью {}кнопки панели инструментов (я сделал это для вас). Кнопка «Цитировать» не сохраняет пробелы и не применяет подсветку синтаксиса.
Альваро Гонсалес,