Какой ваш любимый трюк программирования на C?

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

/ * Вызывает ошибку компиляции, если условие истинно * /
#define BUILD_BUG_ON (условие) ((void) sizeof (char [1-2 * !! (условие)]))

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

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

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

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

#ifdef DEFINE_MYHEADER_GLOBALS
#define ГЛОБАЛЬНЫЙ
# определить INIT (x, y) (x) = (y)
#еще
#define GLOBAL extern
#define INIT (x, y)
#endif

ГЛОБАЛЬНОЕ int INIT (x, 0);
ГЛОБАЛЬНЫЙ 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.

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

Ответов (25)

Я фанат хаков 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;

и т.п.

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

Использование иначе бессмысленно ? : оператор для инициализации константной переменной

const int bytesPerPixel = isAlpha() ? 4 : 3;

Мне всегда нравились тупые уловки препроцессора для создания универсальных типов контейнеров:

/* list.h */
#ifndef CONTAINER_TYPE
#define CONTAINER_TYPE VALUE_TYPE ## List
#endif
typedef struct CONTAINER_TYPE {
    CONTAINER_TYPE *next;
    VALUE_TYPE v;
} CONTAINER_TYPE;
/* Possibly Lots of functions for manipulating Lists
*/
#undef VALUE_TYPE
#undef CONTAINER_TYPE

Тогда вы можете сделать, например:

#define VALUE_TYPE int
#include "list.h"
typedef struct myFancyStructure *myFancyStructureP;
#define VALUE_TYPE myFancyStructureP
#define CONTAINER_TYPE mfs
#include "list.h"

И никогда больше не пишите связанный список. Если VALUE_TYPE всегда будет указателем, то это перебор, поскольку void * будет работать так же хорошо. Но часто бывают очень маленькие структуры, для которых косвенные издержки часто не имеют смысла. Кроме того, вы получаете проверку типа (то есть вы можете не захотеть объединять связанный список строк со связанным списком двойников, даже если оба будут работать в связанном списке void *).

В C99 вы можете напрямую вставлять URL-адреса в исходный код внутри функций. Пример:

#include <stdio.h>

int main(int argc, char** argv) {
    http://stackoverflow.com/
    printf("Hello World");
}

Мне нравятся пустые операторы if-else и while (0).

Например:

#define CMD1(X) do { foo(x); bar(x); } while (0)
#define CMD2(X) if (1) { foo(x); bar(x); } else

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

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

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

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

struct something X = {0};

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

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

Еще один приятный «трюк» препроцессора - использование символа «#» для вывода отладочных выражений. Например:

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

изменить: приведенный ниже код работает только на C++. Спасибо smcameron и Evan Teran.

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

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

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

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

Мне нравится "взлом структуры" для объекта с динамическим размером. Этот сайт тоже довольно хорошо объясняет это (хотя они относятся к версии 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).

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

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

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

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

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

#define return DoSomeStackCheckStuff, return

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

В шапке объявить

#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 работает хорошо

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

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

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

использование __FILE__ и __LINE__ для отладки

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

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

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

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

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

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

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

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

Любые. Я уверен, что многие другие крутые техники, о которых я не думал.

Вот пример того, как сделать так, чтобы код 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;
}

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

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

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

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

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

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

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

Вместо того

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

Использовать

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