KAHIBARO
Discord Login Register

22.2 Null Pointers

Understanding Null Pointers in ROOT

Null pointers are one of the most common immediate causes of crashes in ROOT analysis. Recognizing them early and guarding against them will save you a lot of time and frustration.

What a Null Pointer Is in Practice

In C++ a pointer variable can either point to a valid object in memory or to "nothing." When it points to nothing, its value is nullptr (or 0 in older code). A null pointer is just a pointer that does not refer to any real object.

In ROOT-based analysis you often write code like

cpp
TH1F *h = (TH1F*)f->Get("hEnergy");
h->Draw();

If f->Get("hEnergy") cannot find the object, it returns a null pointer. The pointer h exists, but there is no histogram behind it. The next line tries to call Draw() on "nothing." This is undefined behavior and typically causes a segmentation fault.

Always assume any pointer returned by ROOT can be null.
Always check before you use it.

Typical Places Where Null Pointers Appear in ROOT

Null pointers usually come from ROOT calls that are supposed to return a pointer to an object, but do not find or create what you expect. Some very common sources are:

  1. TFile::Open and file handling
cpp
   TFile *f = TFile::Open("data.root");

If the file does not exist, is corrupted, or cannot be opened, f may be nullptr. Even if it is not null, f->IsZombie() may be true, which means the file is not usable.

  1. TFile::Get and TDirectory::Get
cpp
   TH1F *h = (TH1F*)f->Get("histName");

If "histName" is not found in the file or directory, the returned pointer is null.

  1. TTree::Get, gDirectory->Get, and similar lookup methods
    Any lookup by name can return null if the name is wrong or the object is not there.
  2. Canvases, pads, and global pointers
    Pointers like gPad, gDirectory, or gROOT->FindObject("something") can be null depending on context. For example, gPad is null if no canvas or pad is currently active.
  3. TTrees and branches
cpp
   TTree *t = (TTree*)f->Get("tree");

If there is no TTree called "tree" in the file, t is null. Similarly, branch pointers retrieved via GetBranch can be null if the branch name is wrong.

  1. Dynamic object creation that fails
    In most usual ROOT usage new will either succeed or terminate the program, so new returning null is rare. More often, failures are from ROOT access functions, not memory allocation itself.

Recognizing Symptoms of Null Pointer Problems

Null pointer issues often look like generic ROOT crashes, but there are some recognizable patterns:

  1. Crash when calling a method on a pointer
    The code compiles, runs, and crashes exactly on a line such as:
cpp
   h->Draw();
   t->GetEntry(i);
   canvas->cd(1);

In a debugger, the backtrace will often show the crash inside a method of the class you are calling. This is a classic symptom of a method call on a null pointer.

  1. Immediate crash after Get or Open
    You call something like:
cpp
   TFile *f = TFile::Open("data.root");
   TH1F *h = (TH1F*)f->Get("h1");
   h->Draw();

and the crash happens at h->Draw(). This is often because "h1" is not present, so h is null.

  1. Access to TTrees or branches fails
    You might see:
cpp
   t->SetBranchAddress("energy", &energy);

compile but then crash as soon as you call GetEntry. If t is null because the tree was not retrieved properly, any subsequent use of t is invalid.

  1. Random behavior depending on ROOT session state
    Sometimes a macro "works" when you run other commands before it, but crashes when you run it in a fresh ROOT session. This can happen when your macro relies on global pointers like gPad or gDirectory that are only non-null after you have drawn something or opened a file.

Checking for Null Pointers in ROOT Code

The main defense against null pointer problems is simple checks before you use a pointer.

Checking file pointers

Whenever you open a file, check both for null and for "zombie" state:

cpp
TFile *f = TFile::Open("data.root");
if (!f || f->IsZombie()) {
   std::cerr << "Error: cannot open file data.root" << std::endl;
   return;
}

Rule: Never use a TFile* without checking if (!f || f->IsZombie()) first.

Checking object retrieval from files and directories

When you use Get, always verify the result:

cpp
TH1F *h = nullptr;
f->GetObject("hEnergy", h);   // type-safe version, recommended
if (!h) {
   std::cerr << "Error: histogram hEnergy not found in file" << std::endl;
   return;
}
h->Draw();

If you use Get directly:

cpp
TH1F *h = (TH1F*)f->Get("hEnergy");
if (!h) {
   std::cerr << "Error: hEnergy not found" << std::endl;
   return;
}
h->Draw();

The same pattern applies to trees:

cpp
TTree *t = (TTree*)f->Get("tree");
if (!t) {
   std::cerr << "Error: TTree 'tree' not found" << std::endl;
   return;
}

Checking trees and branches

Before you use branches:

cpp
TBranch *bEnergy = t->GetBranch("energy");
if (!bEnergy) {
   std::cerr << "Error: branch 'energy' not found in tree" << std::endl;
   return;
}

When using SetBranchAddress, also confirm the tree itself is valid:

cpp
if (!t) {
   std::cerr << "Error: null TTree pointer" << std::endl;
   return;
}
t->SetBranchAddress("energy", &energy);

Checking global pointers like gPad and gDirectory

Always assume globals might be null:

cpp
if (!gPad) {
   std::cerr << "Warning: no active pad, creating canvas" << std::endl;
   TCanvas *c = new TCanvas("c", "c", 800, 600);
   c->cd();
}

Similarly, for directories:

cpp
if (!gDirectory) {
   std::cerr << "Warning: no current directory" << std::endl;
} else {
   gDirectory->ls();
}

Debugging a Suspected Null Pointer Crash

When you suspect a null pointer problem in ROOT, a few simple steps often isolate it quickly.

Step 1: Identify the crashing line

If you run macros with ROOT's interpreter, the error message often shows the line number at which the crash occurs. If not, use std::cout or std::cerr prints before likely problem lines to see how far the code gets:

cpp
std::cout << "About to retrieve histogram" << std::endl;
TH1F *h = (TH1F*)f->Get("hEnergy");
std::cout << "Retrieved histogram, about to draw" << std::endl;
h->Draw();

If the second message never appears, the crash is on h->Draw().

Step 2: Print pointer values

Before calling methods, print the pointer:

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

Any pointer printed as 0x0 is null.

Step 3: Use simple conditional checks

Add explicit checks and early returns:

cpp
if (!h) {
   std::cerr << "Histogram pointer is null, aborting Draw()" << std::endl;
   return;
}
h->Draw();

Once you confirm the pointer is null, you can track back to where it was set.

Step 4: Verify object names and file contents

Many null pointer problems are just name mismatches. Use the ROOT browser or TFile::ls() to inspect what is really in the file:

cpp
f->ls();            // lists keys in the file
t->Print();         // prints TTree structure

Check that your code uses exactly the names that appear there, including case and directory paths.

Common Null Pointer Pitfalls in ROOT

It helps to know a few recurring patterns so you can avoid them.

Using a pointer before assigning it

This can happen if you declare a pointer, but never assign it:

cpp
TH1F *h;
// forgot to assign h
h->Fill(1.0);   // crash, h is uninitialized (often effectively null or invalid)

Always assign a pointer before using it. If you do not have an object yet, set it explicitly to nullptr and check:

cpp
TH1F *h = nullptr;
if (!h) {
   std::cerr << "Error: h not initialized" << std::endl;
}

Assuming `Get` always works

It is very easy to write:

cpp
TH1F *h = (TH1F*)f->Get("h1");
h->Draw();

This is convenient in quick interactive work, but for analysis code that you want to re-run reliably, you should always check:

cpp
if (!h) {
   std::cerr << "Error: h1 not found in file" << std::endl;
   return;
}

Using removed or out-of-scope objects

In more advanced code, a pointer can become invalid after the object is deleted or after a file is closed. For example:

cpp
TH1F *h = nullptr;
{
   TFile f("data.root");
   f.GetObject("hEnergy", h);
   // f goes out of scope here and is closed
}
h->Draw();   // h points to an object in a closed file, behavior is undefined

Here h is not null, but still invalid. This is a different bug from null pointers, but it often produces similar crashes.

A simple rule of thumb is to avoid keeping pointers to objects owned by files or directories that will be closed or destroyed. If you must, clone the object so it lives independently:

cpp
TFile f("data.root");
TH1F *hOnFile = nullptr;
f.GetObject("hEnergy", hOnFile);
TH1F *h = (TH1F*)hOnFile->Clone("h_local");
// now h is owned by the current directory, not the file

Safer Coding Practices to Reduce Null Pointer Bugs

There are several habits that systematically reduce null pointer issues in ROOT analysis code.

Always initialize pointers

Initialize pointers when you declare them:

cpp
TH1F *h = nullptr;
TTree *t = nullptr;
TFile *f = nullptr;

This way, if you forget to assign them, checks like if (!h) behave predictably.

Group checks near where pointers are obtained

Immediately after you obtain a pointer from ROOT, check it and handle errors close to where they originate. For example:

cpp
TFile *f = TFile::Open("data.root");
if (!f || f->IsZombie()) {
   std::cerr << "Cannot open data.root" << std::endl;
   return;
}
TTree *t = nullptr;
f->GetObject("tree", t);
if (!t) {
   std::cerr << "TTree 'tree' not found in data.root" << std::endl;
   return;
}

Add simple helper functions

You can write small helper functions to avoid repeating checks:

cpp
TFile* OpenFileChecked(const char* filename) {
   TFile *f = TFile::Open(filename);
   if (!f || f->IsZombie()) {
      std::cerr << "Error opening file: " << filename << std::endl;
      return nullptr;
   }
   return f;
}
TH1F* GetHistogramChecked(TFile* f, const char* name) {
   if (!f) return nullptr;
   TH1F *h = nullptr;
   f->GetObject(name, h);
   if (!h) {
      std::cerr << "Histogram '" << name << "' not found in file" << std::endl;
   }
   return h;
}

Use them like this:

cpp
TFile *f = OpenFileChecked("data.root");
if (!f) return;
TH1F *h = GetHistogramChecked(f, "hEnergy");
if (!h) return;
h->Draw();

This pattern centralizes the checks and makes crashes less likely.

Summary

Null pointers in ROOT almost always come from missing files, missing objects, wrong names, or global pointers that are not set in the current context. They show up as crashes when you call a method on a pointer that is actually pointing to nothing.

You can avoid and diagnose most null pointer problems by:

  1. Initializing all pointer variables.
  2. Checking results of TFile::Open, Get, GetObject, GetBranch, and similar functions.
  3. Printing and inspecting pointers when crashes occur.
  4. Verifying file and object names using ls(), the ROOT browser, or Print().

With these habits, null pointer errors become rare and easy to fix instead of mysterious and time consuming.

Views: 13

Comments

Please login to add a comment.

Don't have an account? Register now!