Problem when filling a TTree with 2D array?

A colleague obtained a strange problem when storing a tree with array.

Would you have an idea on what is happening ?

Here is the reproducible example below.

when executing, it writes correctly :

root [0] .x nt.C
5 3

but when reading the tree : the content of a is not correct :

root [0] TFile f(“test.root”)
(TFile &) Name: test.root Title:
root [1] tt->Scan(“a:b”)


  • Row * Instance * a * b *

  •    0 *        0 * 2.180e-37 *         3 *
    
  •    0 *        1 *         0 *         3 *
    
  •    0 *        2 * 7.006e-45 *         3 *
    

If one does not use a dynamical allocation, it works. With a dynamicall alocation (useful if we wish to decide the size during the execution of the program), it gives a wrong value for a.

int nt()
{
 int n1=10;
 int n2=5;
 
 //with next line, it works : the tree content is correct
 //float a[10][5]; //works

 //with this small block, it does not work : the tree content is not correct
 float **a = new float * [n1];
 for (int i=0;i<n1;i++)
   a[i] = new float[n2];
 
 //end small block

 float b[n1][n2];

 TFile *ff  = new TFile("test.root","recreate");
 TTree *tt = new TTree("tt","tt");
 
 tt->Branch("a", &a, "a[10][5]/F");
 tt->Branch("b", &b, "b[10][5]/F");
 
 for (int i = 0; i < n1; i++) {
   for (int j = 0; j < n2; j++) {
     a[i][j] = 5;
     b[i][j] = 3;
   }
 }

 std::cout << a[0][0] << " " << b[0][0] << std::endl;
 tt->Fill();
 tt->Write();
 ff->Close();

 return 0;
}

ROOT Version: 6.18
Platform: linue
Compiler: Not Provided


Hi @escalier ,
taking a look, will let you know asap.

Cheers,
Enrico

1 Like

So, indeed when using:

 float **a = new float * [n1];
 for (int i=0;i<n1;i++)
   a[i] = new float[n2];

valgrind sees foul play:

==35167== Syscall param write(buf) points to uninitialised byte(s)
==35167==    at 0x72BD6C7: write (in /usr/lib/libpthread-2.33.so)
==35167==    by 0x5184F46: TFile::SysWrite(int, void const*, int) (TFile.cxx:4408)
==35167==    by 0x5179D33: TFile::WriteBuffer(char const*, int) (TFile.cxx:2407)
==35167==    by 0x51B4FCA: TKey::WriteFileKeepBuffer(TFile*) (TKey.cxx:1509)
==35167==    by 0x6074643: TBasket::WriteBuffer() (TBasket.cxx:1289)
==35167==    by 0x6088D8B: TBranch::WriteBasketImpl(TBasket*, int, ROOT::Internal::TBranchIMTHelper*)::{lambda()#1}::operator()() const (TBranch.cxx:3134)
==35167==    by 0x6089260: TBranch::WriteBasketImpl(TBasket*, int, ROOT::Internal::TBranchIMTHelper*) (TBranch.cxx:3189)
==35167==    by 0x608A266: TBranch::WriteBasket(TBasket*, int) (TBranch.h:171)
==35167==    by 0x6081D5C: TBranch::FlushOneBasket(unsigned int) (TBranch.cxx:1195)
==35167==    by 0x6081BBC: TBranch::FlushBaskets() (TBranch.cxx:1147)
==35167==    by 0x611E508: TTree::FlushBasketsImpl() const (TTree.cxx:5163)
==35167==    by 0x612BDCB: TTree::Write(char const*, int, int) const (TTree.cxx:9627)
==35167==  Address 0x11412066 is 198 bytes inside a block of size 32,008 alloc'd
==35167==    at 0x484021F: operator new[](unsigned long) (vg_replace_malloc.c:579)
==35167==    by 0x4B2769F: TBuffer::TBuffer(TBuffer::EMode, int) (TBuffer.cxx:85)
==35167==    by 0x50EFA83: TBufferIO::TBufferIO(TBuffer::EMode, int) (TBufferIO.cxx:51)
==35167==    by 0x50DFBFD: TBufferFile::TBufferFile(TBuffer::EMode, int) (TBufferFile.cxx:88)
==35167==    by 0x607F7FD: TBranch::GetTransientBuffer(int) (TBranch.cxx:529)
==35167==    by 0x606F801: TBasket::TBasket(char const*, char const*, TBranch*) (TBasket.cxx:77)
==35167==    by 0x611B130: TTree::CreateBasket(TBranch*) (TTree.cxx:3699)
==35167==    by 0x60809DB: TBranch::FillImpl(ROOT::Internal::TBranchIMTHelper*) (TBranch.cxx:862)
==35167==    by 0x611BEA0: TTree::Fill() (TTree.cxx:4599)
==35167==    by 0x10A5F2: nt() (nt.C:36)
==35167==    by 0x10A6A4: main (nt.C:44)

But if those lines are changed to:

float a[10][5];

valgrind is happy. I am not sure what’s wrong. @pcanal help? :grinning_face_with_smiling_eyes:

Ah I think I know! When you say the branch has leaflist "a[10][5]/F", TTree expects that that &a points to an area of memory with 50 consecutive floats (as it is when you declare the variable as float a[10][5]).
Instead when you dynamically allocate it each new float[5] is at a different memory location, and TTree ends up reading random bits at the moment of writing the data.

You can check by printing the difference between the addresses of the elements:

std::cout << unsigned(&a[n1-1][n2-1] - &a[0][0]) << std::endl; // prints 76 or 220 on my laptop (could print anything >= 49 really)
std::cout << unsigned(&b[n1-1][n2-1] - &b[0][0]) << std::endl; // will always print 49

@pcanal what’s the right way to dynamically allocate an array of arrays and write it with TTree?

EDIT:
btw stack-allocated arrays of dynamic size such as float b[n1][n2] are a non-standard language extension, you need constexpr int n1=10; constexpr int n2=5; to make it valid “ISO C++”

thank you. The initial code had const and I removed it just as a check.
Your idea of consecutive space it interesting. If so, then TTree would not allow this dyncamic allocation, which is somehow “standard”.

I bet there is a way to make it work, just not Branch("a", &a, "a[10][5]/F"). Let’s see what Philippe suggests.

The syntax:

 tt->Branch("a", &a, "a[10][5]/F");
 tt->Branch("b", &b, "b[10][5]/F");

is not flexible and assume that the arguments describe the content (and does not look at the type of the pointer at all). And, as Enrico mentioned, it assume it is consecutive in memory.

In your example (maybe over-simplified for your real use case), the size are fixed. So you could also use

using f10 = float[10];
f10 *c = new float[5][10];

If you must use the float ** pattern, you will need to wrap it in a struct or class, generate the dictionary for that struct, compile and load it and create a branch based on that struct:

struct_with_array b;
tt->Branch("b", &b);

Note that if the dimension are actually variable, we can only support the outer dimension to be variable (an issue with missing syntax to tell the I/O what is the size of that dimensions).

To have truely variable number of variable dimensions you need to use nested vector.

std::vector<std::vector<float>> d;

Cheers,
Philippe.

1 Like

Thank you very much Philippe et Enrico. I mark problem as solved.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.