KAHIBARO
Discord Login Register

22.9 Debugging ROOT Macros

Understanding Problems in ROOT Macros

ROOT macros are just C++ code that ROOT interprets or compiles for you. Almost every tricky ROOT problem that “mysteriously appears” in a macro reduces to ordinary C++ issues, plus a few ROOT specific pitfalls like object ownership and I/O. This chapter focuses on practical techniques to find and fix problems in your .C files, not on re explaining C++ itself.

Interpreted vs Compiled Macros and Why It Matters

ROOT can execute a macro in two main modes: interpreted and compiled. When you run

cpp
root[] .x myMacro.C

ROOT’s C++ interpreter (cling) parses and runs the code immediately. This is flexible and convenient for development, but you get only interpreter error messages and no direct help from an external compiler.

If you run

cpp
root[] .L myMacro.C+
root[] myMacro();

ROOT compiles your macro with a C++ compiler and links it into the current session. Compilation errors and warnings now come from the compiler itself. These messages are often much stricter and more informative than what cling provides.

Always compile non trivial macros with .L myMacro.C+ during development. Compiler warnings are one of your most powerful debugging tools.

Interpreted mode is useful for quick tests, but if you see strange behavior or crashes in interpreted mode, re run the macro in compiled mode. If the compiler refuses to build it, you have a real C++ error to fix before you can trust any result.

Reading and Interpreting Error Messages

Most debugging starts from an error message. ROOT and C++ compilers print errors in slightly different styles, but a few patterns are very common.

A typical interpreter error looks like

text
Error in <TRint::HandleTermInput()>: 
cling::Interpreter::Evaluate(): 
input_line_12:7:3: error: use of undeclared identifier 'hist'
  hist->Fill(1.0);
  ^

This tells you which input line, which column, and what the problem is. Focus on the part after error:. Here use of undeclared identifier 'hist' means you tried to use a variable that the interpreter does not know about in that scope.

Compiler errors from .L myMacro.C+ often include a file name and line:

text
myMacro.C:42:10: error: no matching function for call to 'Fill'
    h->Fill("string");
         ^~~~

This tells you that at line 42 the call to Fill is not valid for the type of h. In ROOT this usually means you passed arguments of the wrong type or number for that histogram.

Warnings are just as valuable:

text
myMacro.C:55:7: warning: unused variable 'nEvents' [-Wunused-variable]
    int nEvents = tree->GetEntries();
        ^

A warning about unused variables, implicit conversions, or possible null dereferences often points directly to a logic mistake.

When you see a long error list, work from the first serious error, not from the bottom. Later messages are often just consequences of the first problem.

Making Macros Easier to Debug

Macros that are hard to read are hard to debug. Small changes in style can dramatically reduce debugging time.

Use small helper functions instead of one long macro body. For example, instead of

cpp
void myMacro() {
    // 200 lines of code
}

split into separate functions for configuration, I/O, analysis, and plotting. When a problem appears, you can test or temporarily disable one function at a time.

Give variables and objects descriptive names. TH1F hEnergy is more informative during debugging than TH1F h1.

Avoid global variables in macros whenever possible. Globals make it harder to know who changed a value and when. Prefer to pass objects as function arguments.

If you find yourself commenting out large regions of code to track a bug, that is a sign your macro can be split into smaller pieces.

Short, modular functions and descriptive variable names are debugging tools. They make it trivial to isolate and understand faulty behavior.

Using Print Statements for Quick Diagnostics

The simplest debugging tool you have is std::cout and ROOT’s Print() methods. Strategic print statements verify assumptions about variable values, object lifetimes, and code paths.

To check that a function is actually called:

cpp
std::cout << "Entering fillHistograms()" << std::endl;

To print a numeric variable:

cpp
std::cout << "Event " << i << ", energy = " << energy << std::endl;

To inspect a ROOT object:

cpp
hist->Print();

or to check a pointer:

cpp
if (!hist) {
    std::cout << "hist is null!" << std::endl;
}

A useful technique is to print at the beginning and end of loops or functions. If you see the “start” message but not the “end” message, you know the crash or logical error happens inside that region.

Be disciplined about removing or reducing debug output once the issue is resolved, especially in loops over many events. Excessive printing can slow analysis by orders of magnitude and fill your terminal with noise.

Using the ROOT Browser and Object Inspector

ROOT’s graphical tools can help when you suspect problems with created objects, histograms shapes, or canvases but do not immediately see the cause in the code.

You can open the browser from within ROOT:

cpp
root[] new TBrowser();

This lets you explore open ROOT files, TTrees, histograms, graphs, and canvases. You can verify that your macro actually saved the objects you expect, with the right names and in the right directories.

The object inspector can be accessed by right clicking objects in the browser. It displays internal properties like number of entries, axis ranges, and styles. For debugging, this is useful when the macro runs without errors but the plot looks wrong. You can check, for example, whether binning, min and max, or directory ownership are what you intended.

Combining the browser with judicious use of Print() calls often reveals mis named objects, overwritten histograms, or forgotten directory changes.

Debugging Crashes and Segmentation Faults in Macros

If your macro causes ROOT to crash with a segmentation fault, you almost always have an issue with pointers, memory, or object lifetimes. The most common ROOT macro crash patterns are:

Using an uninitialized pointer:

cpp
TH1F *h;
h->Fill(1.0); // crash

Deleting an object and then using it again:

cpp
delete h;
h->Fill(2.0); // crash

Getting a null pointer from a file and not checking:

cpp
TH1F *h = (TH1F*)file->Get("hMissing");
h->Draw(); // may crash if "hMissing" does not exist

To debug such crashes, first run your macro in compiled mode with .L myMacro.C+. Then add pointer checks before using objects:

cpp
if (!h) {
    std::cout << "Error: histogram pointer is null" << std::endl;
    return;
}

If the crash persists and you cannot localize it with print statements, you can use an external debugger like gdb:

bash
gdb root
(gdb) run -l -b -q -x myMacro.C

When ROOT crashes, use:

bash
(gdb) bt

to see a backtrace. The lines that reference your .C file indicate where in your macro the crash occurred.

Any segmentation fault from a ROOT macro should be treated as a serious logic error. Add pointer checks, simplify the macro, and use a debugger until you can explain the crash.

Tracking Logic Errors and Wrong Results

Sometimes the macro runs without errors or crashes but produces wrong numbers or distorted plots. Debugging this kind of logical problem is different from fixing syntax.

A powerful technique is to construct a minimal input and a minimal version of your macro where you know the correct result by hand. For example, create a tiny TTree with 3 events, known values, and then apply your selection and histogramming logic. You can then compute the correct histogram entries and means by hand and compare.

Introduce checksums or counters. For instance, count the number of events that pass each selection step:

cpp
int nTotal = 0;
int nPassEnergy = 0;
for (Long64_t i = 0; i < nEntries; ++i) {
    tree->GetEntry(i);
    ++nTotal;
    if (energy > 10.0) {
        ++nPassEnergy;
        hist->Fill(energy);
    }
}
std::cout << "Total events: " << nTotal << std::endl;
std::cout << "Events with energy > 10: " << nPassEnergy << std::endl;

If an expected fraction of events does not match your physics expectation or a previous version of the code, you know where to look.

Compare intermediate outputs to known good references. For example, if you have an earlier version of a macro that worked, print intermediate histograms or arrays from both versions and compare their contents.

When logic errors persist, temporarily disable later steps. Comment out plotting or file writing and concentrate only on filling histograms correctly. This isolates the true origin of the discrepancy.

Common Macro Specific Pitfalls

ROOT macros have a few recurring failure modes that are worth checking explicitly whenever debugging.

One frequent issue is mismatch between macro function name, file name, and how you run it. If your file is myMacro.C and contains

cpp
void myMacro(int n) { ... }

then you need to call:

cpp
root[] .x myMacro.C(100)

or, in compiled mode:

cpp
root[] .L myMacro.C+
root[] myMacro(100);

If the function name differs from the file name, or you forget parentheses, ROOT may silently run a different function or do nothing.

Another common problem is forgetting that interpreted macros keep their definitions between runs. If you change the body of a function in an already loaded macro, ROOT may still use the old version until you explicitly reload:

cpp
root[] .L myMacro.C+  // reloads and recompiles

or quit and restart ROOT.

Conflicts between macros and ROOT classes also occur. Avoid naming a macro, function, or variable with the same name as a ROOT class, like TFile, TTree, or TH1F. This can confuse the interpreter and lead to obscure error messages when calling methods.

Finally, pay attention to includes inside macros when compiling. If you rely on external headers or standard library classes, explicitly include them at the top of your .C file. Interpreted mode sometimes tolerates missing includes that compiled mode will reject. Always fix the code so it compiles cleanly instead of relying on interpreter leniency.

If a macro behaves strangely, check: correct function name and call, macro reloading, name conflicts with ROOT classes, and missing or inconsistent #include statements.

By consistently compiling macros, reading error messages carefully, adding targeted printouts, using ROOT’s interactive tools, and watching for the common patterns described here, you will be able to diagnose and fix most ROOT macro problems efficiently.

Views: 10

Comments

Please login to add a comment.

Don't have an account? Register now!