Register
handmade.network»Forums»Post your C/C++ macro tricks
475 posts
Post your C/C++ macro tricks
3 years, 5 months ago Edited by ratchetfreak on June 10, 2016, 12:39 p.m.
cmuratori
I am skeptical that these computed gotos are necessary to get the "improved" performance. For example, in MSVC you would just place an __assume(0) in the default: case of a switch and it would no longer emit the bounds-checking code, right? Has anyone done a more thorough analysis as to whether these are interesting?

- Casey


It has to emit the bounds check because __pc__ can be anything (being passed in through pointer) and a switch has well defined behavior in all cases (a empty default being inserted if not present).

There may be hope if you make the default case be undefined behavior and let the optimizer run with that. This of course has the issue that you are relying on the optimizer's behavior for optimal code.

edit: and I should have looked up what __assume(0) does...
Timothy Wright
77 posts / 1 project
Post your C/C++ macro tricks
3 years, 5 months ago Edited by Timothy Wright on June 10, 2016, 2:33 p.m.
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
3 years, 5 months ago Edited by GreenLightning on June 10, 2016, 3:25 p.m.
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.
475 posts
Post your C/C++ macro tricks
3 years, 5 months ago
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
3 years, 5 months ago
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

None
475 posts
Post your C/C++ macro tricks
3 years, 5 months ago
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
3 years, 5 months ago Edited by Caleb on June 11, 2016, 1:09 a.m.
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++.

None
475 posts
Post your C/C++ macro tricks
3 years, 5 months ago Edited by ratchetfreak on June 11, 2016, 1:42 a.m.
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
1983 posts / 1 project
Post your C/C++ macro tricks
3 years, 5 months ago
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
3 years, 5 months ago Edited by Caleb on June 11, 2016, 1:09 p.m.
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));
}

None
stephen goglin
20 posts

aka Quartertron

Post your C/C++ macro tricks
3 years, 5 months ago

None
27 posts
Post your C/C++ macro tricks
3 years, 5 months ago
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
3 years, 5 months ago
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
3 years, 5 months ago Edited by mojobojo on June 15, 2016, 7:51 p.m.
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;
}
475 posts
Post your C/C++ macro tricks
3 years, 5 months ago
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.