Какой твой любимый трюк?

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

/* Force a compilation error if condition is true */
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

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

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

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

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

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

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

Ответ 1

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

При чтении исходного кода Quake 2 я придумал что-то вроде этого:

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

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

С тех пор перед моими глазами открылся новый мир творческого использования препроцессора. Я больше не включаю только заголовки, но целые куски кода время от времени (он многократно увеличивает повторное использование): -p

Спасибо, Джон Кармак! XD

Ответ 3

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

struct something X = {0};

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

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

Ответ 4

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

Ответ 5

с помощью __FILE__ и __LINE__ для отладки

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

Ответ 6

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

Ответ 7

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

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

#define return DoSomeStackCheckStuff, return

Ответ 8

Мне нравится "struct hack" для того, чтобы иметь объект с динамическим размером. Этот сайт тоже очень хорошо объясняет (хотя они относятся к версии C99, где вы можете написать "str []" в качестве последнего члена структура). вы можете сделать строку "object" следующим образом:

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

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

Ответ 9

Объектно-ориентированный код с C, путем эмуляции классов.

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

Ответ 10

Вместо

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

Использование

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

Ответ 11

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

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

Ответ 12

Для создания переменной, доступной только для чтения во всех модулях, кроме той, которую она объявила:

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

Ответ 13

Бит-сдвиги определяются только до значения сдвига 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;
}

Ответ 14

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

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

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

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

Ответ 15

Другим приятным препроцессорным "трюком" является использование символа "#" для печати отладочных выражений. Например:

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

edit: приведенный ниже код работает только на С++. Благодаря smcameron и Эван Теран.

Да, время компиляции всегда велико. Его также можно записать в виде:

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

Ответ 16

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

Ответ 17

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

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

Ответ 18

Я поклонник xor hacks:

Смените 2 указателя без указателя третьей температуры:

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

Или мне очень нравится связанный с xor список только с одним указателем. (Http://en.wikipedia.org/wiki/XOR_linked_list)

Каждый node в связанном списке - это Xor предыдущего node и следующий node. Чтобы перемещаться вперед, адрес узлов находится следующим образом:

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;

и др.

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

Ответ 19

Это происходит из книги "Достаточно веревки, чтобы стрелять себе в ногу":

В заголовке объявить

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

В ваших инструкциях по тестированию кода, например:

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

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

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

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

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

Ответ 20

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

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

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

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

Ответ 21

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

Ответ 22

Не определен для C, но мне всегда нравился оператор XOR. Одна интересная вещь, которую он может сделать, это "своп без значения temp":

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

Ответ 24

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

Взгляните на include/linux/list.h для реальных примеров.

Ответ 25

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

Ответ 26

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

Ответ 27

Наша кодовая база имеет трюк, подобный

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

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

Ответ 28

Развлечения с помощью макросов:

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

Ответ 29

Вот пример того, как сделать код C совершенно не осведомленным о том, что на самом деле используется HW для запуска приложения. Main.c делает настройку, а затем свободный слой может быть реализован на любом компиляторе/арке. Я думаю, что это довольно аккуратно для абстрагирования кода C немного, поэтому он не может быть для spesific.

Добавление полного компилируемого примера здесь.

/* 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;
}

Ответ 30

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

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