's journal
Posts tagged c
Macros in C/C++, the right way.
Dec 12th
When used appropriately macros are very useful, yet they are very easy to misuse. Before getting into cons and pros, first lets make sure if we really know what is a macro and how does it work.
Roughly there are three stages of compilation in C; preprocessing, compiling, linking. It really makes sense and it is very easy to understand, don’t think of this as a complex deal. We’ll mostly talk about the preprocessor stage in this post. As the name implies, this stage just pre-processes the source code before compiling it, it all happens prior to compiling and that’s it. Preprocessing includes defining some macros, and replacing each macro found in the source code with its value, so that it can be compiled. So get this right once and for all, macros are processed before compilation, they won’t be in the run-time code.
As an exercise you can use -E parameter of GCC, which will make it stop right after the preprocessing stage.
Now, let’s begin with a few examples;
#define ERROR_SEEK 152
if( errno == ERROR_SEEK )
{
// Handle the error.
}
This directive defines a macro ERROR_SEEK. Preprocessor will replace every occurrences of ERROR_SEEK with 152 prior to compiling. So, the code that is going to be sent to compiler will have “if( errno == 152)” not the ERROR_SEEK because the compiler simply does not know what ERROR_SEEK is. Nothing fancy, dead simple. But it makes the code much more readable. Look at this example;
#ifdef __GNUC__ #define COMPILER "gcc" #else #define COMPILER "proprietary" #endif
If __GNUC__ is defined somehow then every occurrences of COMPILER will be replaced with “gcc” and “proprietary” otherwise. Please note that this all happens before compilation, and this will not result in any executable code. In this special case __GNUC__ is defined by GCC itself. Though we can define a macro via #define directive in the source code, or with -D parameter of the compiler (which will work on most compilers).
Actually the name preprocessor says it all. All these preprocessor directives are pre processed before compilation. Makes sense uh ? Now I think we begin to understand the nature of macros/preprocessors.
Macros can be used to
- Improve readability (ERROR_SEEK is much more meaningful than 152)
- Improve maintainability (When you change ERROR_SEEK in one header, it will be replaced all over the source code)
- Ensuring that a block of code is inlined (more on this later)
Though, if misused, the first two list items above will do just the opposite :)
As for the possible disadvantages of preprocessors
- Could make debugging harder as lines of source lines before and after the preprocessing will be different, so debugger can be confused while stepping the executable code and matching which source line of code it is.
- It is easy to misuse preprocessors.
- Could cause trouble to static code analyzers.
Now, possible misuse scenarios:
1. Operator precedence
The most common misuse scenario which you’ll see in almost every C book is this;
#define FOO_CONST 83+22
printf("%d\n", FOO_CONST * 5 ); // This macro will expand to 83 + 22 * 5, hence will result 193.
One could expect the result to be printed 525. But operator precedence would make the calculation 22 * 5 first, then add 83 to the result, so you’ll see 193 as a result instead of 525. Fixing this problem is easy;
#define FOO_CONST (83+22)
printf("%d\n", FOO_CONST * 5 ); // This macro will expand to (83+22) * 5, hence will result 525.
Enclosing the value of a macro is an easy way to ensure that they are evaluated in the right order.
2. Multiple statements
You can use multiple-statement macros to force inlining of a code block. Though note that this will increase the code size of your program. Anyway, the problem with the multiple-statements is a less known problem. Now, look at this.
#define HELLO(X) printf("Hello "); printf( X "\n" );
The problem with this code is, if you want to conditionally run this code with “if” this code will not do what you intend to do.
if(0)
HELLO("world"); // You'd expect that this HELLO() is never "executed".
Above code will expand into
if(0)
printf("Hello ");
printf( "world" "\n" );
As you can see in the above example only the first statement in the macro is conditionally executed, this is not what we intended for. Above code will always print “world\n”.
So, first thing comes to mind is to put them in curly brackets. Now let’s evaluate that.
#define HELLO(X) { printf("Hello "); printf( X "\n" ); }
Above code looks fine at first glance, but it will also introduce a sneaky bug that will result in compilation error. Now imagine above macro is used like this;
if(1)
HELLO("world");
else
HELLO("baby");
This will expand into;
if(1)
{
printf("Hello ");
printf("world" "\n");
}; // WE GOT ERROR HERE
else
{
printf("Hello ");
printf("baby" "\n");
};
So, here’s the trick which works perfectly. It is the do{} while(0) trick. OK, let’s see.
#define HELLO(X) do { printf("Hello "); printf( X "\n"); } while(0)
You can use this as you please, imagine the if else scenario.
if(1)
HELLO("world");
else
HELLO("baby");
This will expand into;
if(1)
do { printf("Hello "); printf( "world" "\n" ); } while(0);
else
do { printf("Hello "); printf( "baby" "\n" ); } while(0);
So do {} while(0) does not break when you place a semi-colon after it and also has a scope. So it’s a ideal for making sure your multiple-statements are executing as you expected.
Well, I guess that’s all I’ve got to say.
PENSE – oPEN Simulation Environment
Dec 6th
Now, that I got a IDE/SATA USB case, I started looking at my very old HDDs. I found very old codes of mine, this is one of them. PENSE (oPEN Simulation Environment) was my thesis project. It is a framework which you can use to implement simulation easily. I wrote it in C++. Only dependency is GNU’s libmatheval to implement algorithms out of mathematical expressions easily. I even wrote documentation in LaTeX! :)
Anyway here’s libpense and pensedemo. Please note the autoconf masterpiece in the libpense :) it was a bitch to get it working but once it is working… well, it works. I remember compiling these codes on WIN32, Mac OS X and GNU/Linux without a single problem. Yes, I was young and stupid. I developed this on GNU/Linux :)
Oh, the documentation in LaTeX, PDF and the presentation in PPT format is available. Also there’s a reference manual for libpense, I guess I just had too much time :)
A-hem, and you have to excuse any lameness you can spot, since this is a 4-year old code ;)
A sample code from pensedemo;
Environment env;
Device::Source::PWM pwm( "pwm", &env );
pwm.setOn( true );
pwm.setFrequency( pwm_freq );
Device::Source::VoltageSource vs( 0, 4.8, "voltage source", &env );
vs.setOn( true );
vs["output"] = 4.8;
Device::Plant::DCMotor motor( "Maxon_118465", &env );
motor.setLoad( "0.0" );
motor["J_r"] = 0.0000000503;
motor["k_n"] = 252.374609;
motor["I_o"] = 0.029;
motor["V"] = 0.0;
motor["R"] = 2.16;
Device::Controller::FuzzyLogic f( 3, "fuzzy logic controller", &env );
f.setSetPoint( set_point );
f.setInputDomainWidth( 5 );
f.setOutputDomainRange( 0, 100 );
// This is where we connect the devices together to form a feedback loop.
// We connect the PWM controller to the Voltage Source so that PWM can turn
// the VS on and off. Then we connect the voltage source to the DC Motor, so
// that it can, well, run. Then we connect the angular velocity parameter of the
// motor to the Fuzzy Logic controller, so that it can adjust the PWM controller
// and control the speed of the motor.
connect( &pwm, "output", &vs, "on" );
connect( &vs, "output", &motor, "V" );
connect( &motor,"w", &f, "input" );
connect( &f, "output", &pwm, "duty" );