It depends on the memory allocator's status, it's a black box for programmers . For your code snippet, we can check the corresponding assembly:
f(): # @f()
push rbp
mov rbp, rsp
sub rsp, 16
mov edi, 80
call operator new(unsigned long)
mov qword ptr [rbp - 8], rax
mov dword ptr [rbp - 12], 0
Only the operator new is called, since the struct is a plain old data type, we don't have a non-trivial constructor called here, the memory we allocated is untouched.
If we change the code slightly by inserting a memory allocation and deallocation with exactly same size with your struct:
#include <cstring>
#include <iostream>
#include <memory>
const size_t n = 10;
struct node {
node *arr[n];
};
void f() {
{
std::unique_ptr<char[]> up(new char[sizeof(node)]);
memset(up.get(), 0xff, sizeof(node));
}
node *curr = new node;
for (int i = 0; i < n; ++i) {
if (curr->arr[i] != nullptr) std::cout << "not null" << std::endl;
}
}
int main(int argc, char *argv[]) {
f();
return 0;
}
We get garbage values now, this is because that we have allocated a memory block with the size sizeof(node), and fill it with value then deallocate it immediately.
For modern memory allocators, they tend to retain a memory pool for different size ranges to boost the performance by avoiding too frequent system call (You may reference the design for jemalloc, the one in libc is similar but much simpler). In my code, we allocated a memory block with the size sizeof(node) and deallocate it, the memory will be held by the allocator. Then we allocate a node with node *curr = new node;, it's likely that we will get the same address returned by the previous allocation. So we get the expected garbage value now, it will be a different case for complex programs with multiple threads, we will ignore the complex scenario here.
To initialize variable in c++ is a good habit and can avoid many related bugs, I suggest always do it.
Online demo.
delete, you are more likely to see zeros. Never any guarantees!)