Handmade Network»Forums
Timothy Wright
76 posts / 1 project
Post your C/C++ macro tricks
Edited by Timothy Wright on
The KISS engine has some fun macros for debugging and testing code (using code from "Learn C the Hard Way")

First are the debug macros which I use all the time:

 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
#ifndef __dbg_h__
#define __dbg_h__

#include <stdio.h>
#include <errno.h>
#include <string.h>

#ifdef NDEBUG
#define debug(M, ...)
#else
#define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#endif

#define clean_errno() (errno == 0 ? "None" : strerror(errno))

#define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

#define log_warn(M, ...) fprintf(stderr, "[WARN] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

#define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)

#define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }

#define sentinel(M, ...)  { log_err(M, ##__VA_ARGS__); errno=0; goto error; }

#define check_mem(A) check((A), "Out of memory.")

#define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__); errno=0; goto error; }

#endif


Then there is the unit testing that I just paste into whatever file I need.

 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
#undef NDEBUG
#ifndef _minunit_h
#define _minunit_h

#include <stdio.h>
#include <dbg.h>
#include <stdlib.h>

#define mu_suite_start() char *message = NULL

#define mu_assert(test, message) if (!(test)) { log_err(message); return message; }
#define mu_run_test(test) debug("\n-----%s", " " #test); \
    message = test(); tests_run++; if (message) return message;

#define RUN_TESTS(name) int main(int argc, char *argv[]) {\
    argc = 1; \
    debug("----- RUNNING: %s", argv[0]);\
        printf("----\nRUNNING: %s\n", argv[0]);\
        char *result = name();\
        if (result != 0) {\
            printf("FAILED: %s\n", result);\
        }\
        else {\
            printf("ALL TESTS PASSED\n");\
        }\
    printf("Tests run: %d\n", tests_run);\
        exit(result != 0);\
}


int tests_run;

#endif


Which is a little janky but fun. Used like this:

 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
#include "minunit.h"
#include "math.h"
#define KISS_VEC_IMP
#include "kiss_vec.h"

#define EPSILON	0.000001f
#define EQ(a,b) (fabs((a) - (b)) < EPSILON)

char* test_vec_add() {

	vec v0 = vec3( 1.0f, 2.0f, 3.0f );
	vec v1 = vec3( 2.0f, 3.0f, 5.0f );
	vec_add( &v0, &v0, &v1 );

	mu_assert( EQ( v0.x, 3.0f ), "x not equal" );
	mu_assert( EQ( v0.y, 5.0f ), "y not equal" );
	mu_assert( EQ( v0.z, 8.0f ), "z not equal" );

	return NULL;
}

char *all_tests() {
	mu_suite_start();

	mu_run_test(test_vec_add);

	return NULL;
}

RUN_TESTS(all_tests);


Again, not mine, adapted from examples: Learn C the Hard Way

Here's the handmade project (which will have more stuff soon): https://kiss.handmade.network/
5 posts
Post your C/C++ macro tricks
Edited by GreenLightning on
I like the function signature macro which I learned from Casey's Working on The Witness blog series (see section Filter Tables of part 15). It is useful if you have a bunch of functions with the same signature. For example a command line utility I wrote has a function for each command the user can issue. The main function reads in the first argument and calls the appropriate function for that command with the remaining arguments. The macro itself looks like this:

1
#define COMMAND(name) void name(int argc, const char* argv[])


To create a new command:

1
2
3
4
5
6
7
// declaration
COMMAND(do_something);

// definition
COMMAND(do_something) {
  // code
}


If you need to deal with function pointers, you can also do this:

1
typedef COMMAND(Command);


This means that the function prototype is only defined in the macro and all the declarations and definitions automatically match. If I wanted to add another argument, I would only need to change the macro and the calls in the main function. Also, if I want to create a new command, I just need to write COMMAND(do_something_else) and don't have to remember the exact prototype.
511 posts
Post your C/C++ macro tricks
GreenLightning
I like the function signature macro which I learned from Casey's Working on The Witness blog series (see section Filter Tables of part 15). It is useful if you have a bunch of functions with the same signature. For example a command line utility I wrote has a function for each command the user can issue. The main function reads in the first argument and calls the appropriate function for that command with the remaining arguments. The macro itself looks like this:

1
#define COMMAND(name) void name(int argc, const char* argv[])


To create a new command:

1
2
3
4
5
6
7
// declaration
COMMAND(do_something);

// definition
COMMAND(do_something) {
  // code
}


If you need to deal with function pointers, you can also do this:

1
typedef COMMAND(Command);


This means that the function prototype is only defined in the macro and all the declarations and definitions automatically match. If I wanted to add another argument, I would only need to change the macro and the calls in the main function. Also, if I want to create a new command, I just need to write COMMAND(do_something_else) and don't have to remember the exact prototype.


But it has the downside that the argument names aren't immediately available. To me that's a bigger issue than needing to copy paste a few times.
Caleb
12 posts
None
Post your C/C++ macro tricks
Since clang/gcc like to complain about c style casting at the highest warning levels I have these macros in my code

1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
#  define scast(t, v) static_cast<t>(v)
#  define rcast(t, v) reinterpret_cast<t>(v)
#  define ccast(t, v) const_cast<t>(v)
#else
#  define scast(t, v) ((t)(v))
#  define rcast(t, v) ((t)(v))
#  define ccast(t, v) ((t)(v))
#endif


for static assertion that works in c and c++, i prefer the following (found on stack overflow) over the typedef array trick

1
2
3
4
5
6
7
8
#define CAT_2_TOKENS_2(A, B) A ## B
#define CAT_2_TOKENS(A, B) CAT_2_TOKENS_2(A, B)

#ifndef __cplusplus
#define static_assert(condition, message) \
   typedef struct { int CAT_2_TOKENS(static_assertion_failure__, message) : !!(condition); } \
   CAT_2_TOKENS(static_assertion_, __COUNTER__)
#endif


And some pack/warning stuff. It should be noted that DISABLE_LLVM_WARNING works on gcc and clang

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#if defined(_MSC_VER) && !defined(__clang__)
#  define PUSH_PACK(V)             __pragma(pack(push, V))
#  define POP_PACK                 __pragma(pack(pop))
#  define PUSH_WARNING             __pragma(warning(push))
#  define POP_WARNING              __pragma(warning(pop))
#  define PUSH_WARNING_LEVEL(V)    __pragma(warning(push, V))
#  define DISABLE_MSVC_WARNING(V)  __pragma(warning(disable: V))
#  define DISABLE_LLVM_WANRING(V)
#else
#  define DO_PRAGMA(X)             _Pragma(#X)
#  define PUSH_PACK(V)             DO_PRAGMA(pack(push, V))
#  define POP_PACK(V)              DO_PRAGMA(pack(pop))
#  define PUSH_WARNING             DO_PRAGMA(GCC diagnostic push)
#  define POP_WARNING              DO_PRAGMA(GCC diagnostic pop)
#  define PUSH_WARNING_LEVEL       DO_PRAGMA(GCC diagnostic push V)
#  define DISABLE_MSVC_WARNING(V)
#  define DISABLE_LLVM_WARNING(V)  DO_PRAGMA(GCC diagnostic ignored #V)
#endif
511 posts
Post your C/C++ macro tricks
For rcast you could do something like *((t*)((void*)(&(v))))

it only works on lvalues but makes sure that you can get the bit representation of floats into an int with rcast(int32, myFloat32)
Caleb
12 posts
None
Post your C/C++ macro tricks
Edited by Caleb on
ratchetfreak
For rcast you could do something like *((t*)((void*)(&(v))))


i am not sure you understand the point of reinterpret_cast (or the problem this is trying to solve), which is used for converting pointers to other pointers, and for converting pointers to integers and vis versa. It is not the same thing as taking a reference, doing a pointer cast, then doing a dereference.

The motivation behind these macros is that on the highest possible warning levels (-Wall -Wpedantic -Wextra -Werror) on clang, using c style casts is prohibited, and instead they want you to use static/reinterpret/const cast. Since those aren't available to c and I don't want to arbitrarily disable the warning across the board I use these macros for casting. They are terse, easy to grep, display intent of the cast and they work in both c/c++.
511 posts
Post your C/C++ macro tricks
Edited by ratchetfreak on
cubercaleb


i am not sure you understand the point of reinterpret_cast (or the problem this is trying to solve), which is used for converting pointers to other pointers, and for converting pointers to integers and vis versa. It is not the same thing as taking a reference, doing a pointer cast, then doing a dereference.

The motivation behind these macros is that on the highest possible warning levels (-Wall -Wpedantic -Wextra -Werror) on clang, using c style casts is prohibited, and instead they want you to use static/reinterpret/const cast. Since those aren't available to c and I don't want to arbitrarily disable the warning across the board I use these macros for casting. They are terse, easy to grep, display intent of the cast and they work in both c/c++.


But reinterpret_cast as defined by C++ must be able to roundtrip as long as all the intermediate types used are large enough to hold it. That's what my comment was about. If you introduce this to other C++ programmers they will expect rcast to follow those rules.

rcast(float32, rcast(int32, myFloat32)) under the original C definition will not preserve the float while the C++ definition will.
Mārtiņš Možeiko
2559 posts / 2 projects
Post your C/C++ macro tricks
ratchetfreak, reinterpret_cast cannot be done from int to float (and back). One of types must be pointer (or reference).
Caleb
12 posts
None
Post your C/C++ macro tricks
Edited by Caleb on
ratchetfreak

But reinterpret_cast as defined by C++ must be able to roundtrip as long as all the intermediate types used are large enough to hold it. That's what my comment was about. If you introduce this to other C++ programmers they will expect rcast to follow those rules.

rcast(float32, rcast(int32, myFloat32)) under the original C definition will not preserve the float while the C++ definition will.


c++ reinterpret_cast cannot be used for converting floats <-> ints, as I said earlier it can be used for pointers <-> ints and pointers <-> pointers. With reinterpret_cast to go from float to int you need:

1
*reinterpret_cast<uint32_t*>(&my_float)


Which I have a functions for (they work on lvalues and rvalues)

1
2
3
4
5
6
7
uint32_t FloatBitsToUint32(float Value) {
  return(*rcast(uint32_t *, &value));
}

float Uint32ToFloatBits(uint32_t Value) {
  return(*rcast(float *, &value));
}
27 posts
Post your C/C++ macro tricks
Today I wanted to have an infinite loop without generating a warning with /W4 and without suppressing the specific warning like what while(1) and for (;;) give. I usually use a bool with a while loop and it makes the compiler happy. Decided I do that enough to constitute a macro in my platform header just for this. I found some macro voodoo for making a unique named variable so I decided to take advantage of it.

1
2
3
4
5
6
7
8
// NOTE(joey): This is some fucking magic vodoo macro shit I found here
// http://stackoverflow.com/questions/1082192/how-to-generate-random-variable-names-in-c-using-macros
#define PP_CAT(a, b) PP_CAT_I(a, b)
#define PP_CAT_I(a, b) PP_CAT_II(~, a ## b)
#define PP_CAT_II(p, res) res
#define gp_UNIQUE_NAME(base) PP_CAT(base, __LINE__)

#define gp_InfiniteLoop int gp_UNIQUE_NAME(inf_) = 1; while (gp_UNIQUE_NAME(inf_))


use it like so
1
2
3
gp_InfiniteLoop {
    // fun infinite code here
}


Andre
15 posts
Post your C/C++ macro tricks
mojobojo
Today I wanted to have an infinite loop without generating a warning with /W4 and without suppressing the specific warning like what while(1) and for (;;) give.


As far as I'm aware, no major compiler currently issues a warning when using "for (;;)" to indicate an infinite loop. In fact, that's probably the biggest reason to prefer this syntax to something like "while (1)". What version of MSVC are you using where this is not the case?
27 posts
Post your C/C++ macro tricks
Edited by mojobojo on
iffy

As far as I'm aware, no major compiler currently issues a warning when using "for (;;)" to indicate an infinite loop. In fact, that's probably the biggest reason to prefer this syntax to something like "while (1)". What version of MSVC are you using where this is not the case?


for (;;) was causing an unreachable code warning in something like.

1
2
3
4
5
6
7
int func() {
    for (;;) {
        // do something
    }

    return 0;
}
511 posts
Post your C/C++ macro tricks
mojobojo


for (;;) was causing an unreachable code warning in something like.

1
2
3
4
5
6
7
int func() {
    for (;;) {
        // do something
    }

    return 0;
}


Well then don't put the return in there or use a break in the for loop.
Barret Gaylor
31 posts
I am a game designer who recently became interested in coding in a handmade way. I am not that experienced, but I want to learn.
Post your C/C++ macro tricks
This might not be the best place to ask this but since we are all talk about it, what would be a good resource for learning about how macros work along with the other pre compiler stuff?
Daniel Rasmussen
6 posts
Post your C/C++ macro tricks
Edited by Daniel Rasmussen on
A big pro-tip would be to set the -E compiler flag. That will send the preprocessed file to stdout, so you can inspect that the macros expands as you expect. Just be aware that everything becomes a massive file and C library includes of stdio.h will put a massive amount of code into that file.

So with that you can try the samples from this thread. Fundamentally macros is just text replacement. Which is easy to understand, but also easy to mess up.