You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
While small and simplistic, it has a huge impact for many reasons
#definelz_safe_free(_var, _freefn) do { \
_freefn((_var)); \
(_var) = NULL; \
} while (0)
What does it do?
It free's the variable _var using the second argument _freefn.
_var is then set to NULL. You can find potentially silly memory corruption bugs by crashing on a dereference instead of waiting around for your allocator to realize something isn't right here which can take a while.
C allows you to optionally omit brackets as long as the it contains only one conditional
if (var==1)
do_something();
else()
do_other();
While I don't condone such things, you see it everywhere. Do the do {} while(0) is to avoid this type of issue. This is common practice and does not affect performance.
Anothe reason would be something as simple as localization of variable names and potential collisions.
If you have spent the time to learn proper design, it is a good practice to keep your structures hidden, which can be accessed or modified via accessor/set type functions.
This is a common mistake even experienced programmers still make; ocne you have given a user the ability to access all the little detailed parts of your structures and design in a public manner, they WILL use them. But what happens when you want to change something around; names wise, include order, or whatever. Your users may suddenly find themselves in a bit of trouble. So doing it right the first time around is better than begging for forgiveness 2 years down the line. Something I learned very quickly with libevhtp.
But if you let your preprocessor generate all this code that is just boring to write, rename, and avoid all those stupid ordering of headers with a simple macro.
In the example I gave in the tweet, I had a C file, and a header file, I will shorten it up a bit here, but you can see the longer version still.
This is a very basic example, you can of course create much more complex code generators.
But no matter what, your header file with the function definitions will always be trivial:
#ifndef__BLAH_H__#define__BLAH_H__structhidden_data; /* note we do not expose the data within */#defineGEN_ACCESSOR(vname, vtype) \
vtype hidden_data_get_ ## vname(struct hidden_data *)
GEN_ACCESSOR(stuff, constchar*);
GEN_ACCESSOR(things, bool);
GEN_ACCESSOR(foo, uint8_t);
GEN_ACCESSOR(bar, uint16_t);
GEN_ACCESSOR(baz, uin532_t);
#endif
To see this type of method applied in the most extreme of manners, check out the <sys/tree.h> or <sys/queue.h> header files. These are full on hardcore code generators for various list and tree algorithms.
I think I saw @nmathewson try to do a hashing mechanism using pure preprocessor generation, I may have secretely bonked him on the head.
TL;DNR: no matter who you are, and I've heard about the "overhead" of such abstractions, but I've never seen any noticiable issues, even in the most extreme of cases. Chase down your shitty string, and memory twiddling functions. It's not things like this, I assure you.
Ok, I know, this is extreme abuse, like kicking puppies. But it's good to know, and comes in useful when in certain situations I will explain.
Everyone, at some point, has wanted to have their own version of some system call, but had to do all kinds of stupid things like ld_preloads and garbage like that (even as far as writing kmods, this is bad juju kids).
So here is a useful hack to rid yourself of such silliness
You can use this to over(write|load) commonly used syscalls with your own. For example:
#defineOVERLOAD_SYMBOL(x, y) \
typeof(x)(x)__asm__(y)
staticcharinitialized=0;
statocvoid*sym_handle=NULL;
staticint (*openfn)(constchar*, int, ...);
voidinit(void) {
if (sym_handle) {
return;
}
sym_handle=dlopen("libc.so.6", RTLD_LAZY);
openfn=dlsym(sym_handle, "open");
initialized=1;
}
inlineintmy_open(constchar*pathname, intflags, ...) {
va_listap;
mode_tmode;
va_start(ap, flags);
{
mode=va_arg(ap, mode_t);
}
va_end(ap);
/* do your own stuff here */return (openfn)(pathname, flags, mode);
}
OVERLOAD_SYMBOL(my_open, "open");
Now any library which is linked to this: open is actually my_open without some of the whack steps people go through for the same type of functionality.
I recently came across a preprocessor hack which resulted in an Elvis sneer, headcock, squinted eyes, and a raised eyebrow. Sure, I've shown off some nifty things here. Some of which can be contrived as something they would never use in real code. But there was something about this one that made me want to slam my head into a brick wall to forget what I had just seen.
This is actually standard practice for temporary variable names.
But if you're constantly using it, what a waste of precious stack space.
The following is not the actual code, but it's the basic constructs of what the original developer wrote.
The first thing you've probably noticed is that it relies on c99. OK, I'm fine with that. We all have our own opinions, but to me, having your declarations haphazardly placed wherever they happen to land is ugly as fuck. So let's just skip past that part before I rage out.
The next smug trickery here is the use of declaring "random" variable names by using the standard predefined __LINE__ macro. Let's go through this mess.
Assume our call to U sits on line 12, using the orignal code (int U(i) = 0;)
U is simply an alias to UNIQ
UNIQ takes a single argument, in the case of this example it is a variable declaration i
UNIQ(i) aliases to UNIQ1(i, __LINE__), which in turn is an alias to UNIQ2(i, __LINE__)
UNIQ2(i, __LINE__) finally merges the two things together like so i ## __LINE__
If all goes well, we see the following:
inti12=0;
This is NOT the oddest way to achieve "temporary variables" I've ever seen, thank god.
Let's head for the low hanging fruit here. U is just garbage code. The only usecase is to confuse fellow programmers more than they already are. This isn't amateur hour, toss it aside.
#defineUNIQ2(X, Y) X ## Y
#defineUNIQ1(X, Y) UNIQ2(X, Y)
#defineUNIQ(X) UNIQ1(X, __LINE__)
#defineFOR_EACH(max) \
for (int UNIQ(i) = 0; UNIQ(i) < max; UNIQ(i)++) \
printf("%d\n", UNIQ(i))
The output is the same, aside from the variable now being declared as i11:
This is fine, all in all it's short and sweet with some extra added stack waste:
#include<stdio.h>#include<stdlib.h>#defineUNIQ2(X, Y) X ## Y
#defineUNIQ1(X, Y) UNIQ2(X, Y)
#defineUNIQ(X) UNIQ1(X, __LINE__)
#defineFOR_EACH(max) \
int UNIQ(i) = 1; \
for (UNIQ(i) = 0; UNIQ(i) < max; UNIQ(i)++) \
printf("%d\n", UNIQ(i))
intmain(intargc, char**argv) {
for (intUNIQ(i) =0; UNIQ(i) <atoi(argv[1]); UNIQ(i)++) {
FOR_EACH(5);
}
return0;
}
Still not a fan of the c99 anarchy, but I guess it works fine as long as you keep these little hacks small. As a small note, have we still not learned that wrapping macros in a do { ... } while(0) is good? Are we to assume all developers are the same? If you disagree, immediately solve everything here: https://github.com/ellzey/c_code_puzzles/
I guess my whole point about this crap is: stop trying to be slick. It's all chest thumping at this point. Clear, and readable code will always survive the evolution of your product, and the revolving doors of developers that follow.
Use these nifty macros ONLY WHEN IT MAKES SENSE, don't give into this sort of tripe just to show off your macro prowess.