Skip to content

Code Reversibility with #ifdef

ericcarden edited this page Mar 12, 2012 · 3 revisions

Introduction

Except for very small, simple code changes, changes must be made easily reversible with #ifdef preprocessor directives. If you aren't sure whether your changes are significant enough to warrant making them reversible, then err on the safe side by making them reversible.

The goal is to write your changes such that commenting out a single #define line of code (in a single header or source file) will exactly undo, or "reverse", your changes. This can be useful when searching for the cause of program misbehavior or when a problem is found in a given code change.

There are two ways to do this: (1) manually and (2) using a script. Both ways are discussed below, but it's strongly recommended that you use the manual approach. Here are some words from Paolo Ventafridda on the subject:

The below script method will probably produce virtually unreadable (by humans) code. Therefore, please, except in an emergency, manually make your code reversible - to make it more human-readable. Use the script only if you can't do it manually, but expect the worst. In the case of unreadable script-generated code, your commit will be refused. See examples in options.h. Those changes were made manually, not using the below script. For two years, I've used this manual approach, and I'll have no mercy if someone claims that he couldn't do likewise his first time: commit refused for sure! - Paolo

Before issuing a pull request to merge your modified code into the project, compile and test it both ways (i.e., both enabled and reversed). Make sure your pull request is to merge the enabled (#define line not commented out) version!


Manually Making Code Reversible

  1. Choose an identifier (a "macro" name) to represent your code change (for the rest of these instructions, we'll use "CALC_ERROR_FIX"). It's a good idea to search the project for this identifier to make sure you've chosen a project-unique one.
  2. Choose the file in which to put the #define line of code. This is the line that, if commented out, will cause the project to compile as if you hadn't made any changes. If your changes involve only one source (*.c or *.cpp) file, then choose that file. Otherwise, choose options.h.
  3. Near the top of the chosen file, add this new line of code: #define CALC_ERROR_FIX
  4. It's probably a good idea to add here some comments containing a brief description of the code change. It's helpful to include the date and your name, too.
  5. If you put the #define line in options.h, then make sure each source (*.c or *.cpp) file you modify "includes" options.h, either directly (using a #include "options.h" line) or indirectly (through inclusion in a file already included in the modified source file).
  6. Use #ifdef and #ifndef directives so that your changes will be undone/reversed if the #define line you created is commented out. Here's an example, assuming that you changed only one line of code (changing "2.0" to "2.1"):
#ifdef CALC_ERROR_FIX
  y = x * 2.1;
#else
  y = x * 2.0;
#endif

As long as your #define line is in effect (i.e., not commented out), the line y = x * 2.1; will be compiled, and y = x * 2.0; will be ignored. If your #define line is commented out, however, the y = x * 2.0; line will be used (and your y = x * 2.1; line ignored).


Using a Script to Make Code Reversible

WARNING: THIS METHOD WILL PROBABLY CREATE CODE THAT'S VERY DIFFICULT TO READ, LIKELY RESULTING IN REFUSAL TO ACCEPT YOUR CHANGES INTO THE PROJECT.

To make it easier to make code changes reversible, this Gist was created. This script automatically creates #ifdef commands for all changes introduced between two Git revisions.

Script Usage:

  1. Make the desired code changes, and commit them to Git in one or more revisions.
  2. Determine revision identifiers for the code before and after your changes.
  3. Run do_reverse.pl REV_BEFORE REV_AFTER CHANGE_LABEL
  4. Modified files will be automatically updated with #ifdef CHANGE_LABEL directives.
  5. It's possible that the script will make line endings in updated files inconsistent. Please check that (and fix it if needed) before the final commit.

Example output:

#ifndef MAP_ZOOM
  if (AutoZoom && DisplayMode != dmCircling) {
#else /* MAP_ZOOM */
  if (zoom.AutoZoom() && !mode.Is(Mode::MODE_CIRCLING)) {
#endif /* MAP_ZOOM */