0%

10 C Tricks

雖然原來名稱叫10 C99 tricks,不過有些跟C99沒關係,不過還是有參考價值。

Ternary operator without middle operand (gnu extension)

1
2
3
4
5
// Instead of
x = x ? x : 10;

// We can use the shorter form:
x = x ?: 10;

個人覺得, 這個樣子更不容易看出程式在寫什麼

Unamed struct for compound type

根據實驗之後,這也不需要C99,C89就能正常運作了
在一般的情形之下,這樣子Compilier會提出警告

1
2
3
struct {
float x, y, z;
};

Clang發出這樣的警告

1
2
3
4
demo.c:5:1: warning: declaration does not declare anything [-Wmissing-declarations]
struct {
^~~~~~
1 warning generated.

但是如果再union / struct當中這樣使用的話就沒有問題,可以依照自己喜歡的方式使用

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef union {
struct { float x, y, z; };
struct { vec2_t xy; };
struct { float x_; vec2_t yz; };
float v[3];
} vec3_t;
#define VEC3(x, y, z) { {x, y, z} }

vec3_t vec = VEC3(1, 2, 3);
// We can access the attributes in different ways.
float x = vec.x;
vec2_t xy = vec.xy;
float z = vec.v[2];

IS_DEFINED macro

無法理解為什麼要設計的這麼複雜..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// As used in the linux kernel.
// A macro that expands to 1 if a preprocessor value
// was defined to 1, and 0 if it was not defined or
// defined to an other value.

#define IS_DEFINED(macro) IS_DEFINED_(macro)
#define MACROTEST_1 ,
#define IS_DEFINED_(value) IS_DEFINED__(MACROTEST_##value)
#define IS_DEFINED__(comma) IS_DEFINED___(comma 1, 0)
#define IS_DEFINED___(_, v, ...) v

// Can be used in preprocessor macros:
#if IS_DEFINED(SOMETHING)
...
#endif

// Or even directly in the code.
// Same effect but looks better.
if (IS_DEFINED(SOMETHING)) {
...
}

Convenience macro for Debuging

其實這個方法不限於OpenGL,像GetLastErrorerrno都可以用類似的方法來確認狀態。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Not really special, but so useful I thought
// I'll put it here. Can also be used with other
// libraries (OpenAL, OpenSLES, ...)
#ifdef DEBUG
# define GL(line) do { \
line; \
assert(glGetError() == GL_NO_ERROR); \
} while(0)
#else
# define GL(line) line
#endif

// Put GL around all your opengl calls:
GL(glClear(GL_COLORS_MASK));
GL(pos_loc = glGetAttribLocation(prog, "pos"));

Array size macro

這個也不世新玩意了

1
2
3
4
5
6
7
8
9
10
11
// Is there any C project that does not use it?
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

// Can be used like this:
int a[] = {0, 4, 5, 6};
int n = ARRAY_SIZE(a); // n = 4

// Warning: does not work with array arguments to functions:
int func(int a[]) {
int nb = ARRAY_SIZE(a); // Would not work!
}

Safe-type macro (uses a gnu extension)

當然這也不是只用於min, max, 還可以用於swap等…弱化版的Template。

1
2
3
4
5
#define min(a, b) ({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; \
})

Passing pointer to unnamed variables to function.

這是一般的寫法

1
2
3
4
void func(const int *arg);
// Instead of using a local variable.
int tmp[] = {10, 20, 30};
func(tmp);

不過可以寫成這樣

1
2
// We can write.
func( (const int[]){10, 20, 30} );

更進一步

1
2
3
// Can be useful with a helper macro.
#define VEC(...) ((const int[]){__VA_ARGS__})
func(VEC(10, 20, 30));

如果搭配上designated initializers威力更大

1
2
3
4
5
6
typedef struct {
float x, y;
} vec2_t;
void func(const vec2_t *arg);
#define make_struct(T, ...) (&(const T){ __VA_ARGS__})
func(make_struct(vec2_t, .y = 9999, .x = 20));

Named initializer, with default values

designated initializers跟Variadic Macros的組合技

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// I use this one all the time when writing
// video game. One of the reason why I
// don't like to use C++.

// Let say we have this struct
struct obj {
const char *name;
float pos[2];
float color[4];
};

// We can write a macro like this one
#define OBJ(_name, ...) \
(struct obj) { \
.name = _name, \
.color = {1, 1, 1, 1}, \
__VA_ARGS__ \
};

// Now we can use the macro to create new objects.
// This one with color defaulted to {1, 1, 1, 1}.
struct obj o1 = OBJ("o1", .pos = {0, 10});
// This one with pos defaulted to {0, 0}.
struct obj o2 = OBJ("o2", .color = {1, 0, 0, 1});

X macros

即使在C++當中,這也是個非常重要的技巧之一,利用Macro來進行Code Generation。
X Macro分成兩部分,一個是彼此相相關連的List,另外一個是巨集,對這個List進行展開動作,而這點Template無能為力。
例如

1
2
3
4
5
6
7
8
9
10
11
#define COLORS \
X(Cred, "red") \
X(Cblue, "blue") \
X(Cgreen, "green")

#define X(a, b) a,
enum Color { COLORS };
#undef X
#define X(a, b) b,
static char *ColorStrings[] = { COLORS };
#undef X

當要新增一種顏色的時候,只需要在COLORS那邊修改,減少了維護和犯錯的可能性。
如果需要更進一步的學習,可以參考

  • X Macro
  • The X Macro
  • The New C: X Macros
  • Reduce C-language coding errors with X macros Part 1 Part 2 Part 3
  • Real-world use of X-Macros

    State machine helper using LINE

    Generator的簡易實現,將S__LINE__來當做State之一。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // This is a great trick.
    // Instead of:

    int iter(int state) {
    switch (state) {
    case 0:
    printf("step 0\n");
    return 1;
    case 1:
    printf("step 1\n");
    return 2;
    case 2:
    printf("step 2\n");
    return 3;
    case 3:
    return -1;
    }
    }

    // We can define:
    #define START switch(state) { case 0:
    #define END return -1; }
    #define YIELD return __LINE__; case __LINE__:;

    // And now the function can be written
    int iter(int state) {
    START
    printf("step 0\n");
    YIELD
    printf("step 1\n");
    YIELD
    printf("step 2\n");
    END
    }