I was trying to solve the following LeetCode question in C: https://leetcode.com/problems/populating-next-right-pointers-in-each-node/
I want to maintain a static array of pointers to the yet unconnected TreeNodes in the tail of each row in the given perfect binary tree so that I can use it and update it while traversing the tree in-order. For this, I need the tree height. I can use the problem constraints to find an upper bound for it, 12
, which is good enough. However, I was aiming at a more general solution in which I can also practice dynamic memory allocation.
I came up with the following attempt (which was accepted):
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *left;
* struct Node *right;
* struct Node *next;
* };
*/
int curr_height = 0;
int height = 0;
int have_height = 0;
struct Node ** ptr_to_table = NULL;
void allocate_direct_table(){
int SIZE = height + 9;
ptr_to_table = calloc(SIZE, sizeof(struct Node *));
struct Node * arr[SIZE];
for(int i=0; i<SIZE; i++){
arr[i] = NULL;
}
*ptr_to_table = arr;
// ptr_to_table = malloc((height + 1) * sizeof(struct Node *));
// memset(ptr_to_table, 0, (height + 1) * sizeof(struct Node *));
}
void process(struct Node* root){
int index = curr_height;
if(ptr_to_table[index]){
ptr_to_table[index] -> next = root;
}
ptr_to_table[index] = root;
root -> next = NULL;
}
void preprocess(){
height = curr_height;
allocate_direct_table();
}
struct Node* connect(struct Node* root) {
if(!root && !have_height){
preprocess();
have_height = 1;
return NULL;
}
if(!root) return NULL;
curr_height ++;
connect(root -> left);
curr_height --;
process(root);
curr_height ++;
connect(root -> right);
curr_height --;
return root;
}
I found a behavior of this code that I could not explain. It involves the dynamic memory allocation in the allocate_direct_table()
function:
- When debugging, I can use
SIZE = height
, and everything works. - When submitting, I get an
AddressSanitizer: heap-buffer-overflow
error, so I have to increase the height. I have no idea why, and I have no idea why9
is enough. It probably has to do with the problem constraints, which means my solution is not general.
Here is the error when submitting the version with SIZE = height
:
Problematic input:
[-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13]
Runtime Error:
=================================================================
==30==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000000178 at pc 0x55979bc921c2 bp 0x7ffd997833a0 sp 0x7ffd99783390
READ of size 8 at 0x603000000178 thread T0
#5 0x7f179e0460b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
0x603000000178 is located 0 bytes to the right of 24-byte region [0x603000000160,0x603000000178)
allocated by thread T0 here:
#0 0x7f179ec8bdc6 in calloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10ddc6)
#7 0x7f179e0460b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
Shadow bytes around the buggy address:
0x0c067fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff8000: fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa 00 00
0x0c067fff8010: 00 00 fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa
=>0x0c067fff8020: 00 00 00 00 fa fa 00 00 00 00 fa fa 00 00 00[fa]
0x0c067fff8030: fa fa 00 00 06 fa fa fa 00 00 00 00 fa fa 00 00
0x0c067fff8040: 00 fa fa fa 00 00 00 fa fa fa 00 00 00 00 fa fa
0x0c067fff8050: 00 00 00 00 fa fa 00 00 00 00 fa fa 00 00 00 00
0x0c067fff8060: fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa 00 00
0x0c067fff8070: 00 00 fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==30==ABORTING
Questions:
- Why does
SIZE = height
not work in debug mode? - Why is it
9
or more that I need to add?
Any help is much appreciated!
Update 1:
I had a mistake in my allocator function. Here's a fix:
void allocate_direct_table(){
int SIZE = height;
ptr_to_table = calloc(SIZE, sizeof(struct Node *));
for(int i=0; i<SIZE; i++){
ptr_to_table[i] = NULL;
}
}
The same questions remain.
Update 2:
Rewrote the code following suggestions from the comments.
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *left;
* struct Node *right;
* struct Node *next;
* };
*/
struct Node ** ptr_to_table = NULL;
int have_height = 0;
struct Node ** allocate_direct_table(int height){
struct Node ** tmp = malloc(height * sizeof(struct Node *));
for(int i=0; i<height; i++){
tmp[i] = NULL;
}
return tmp;
}
void process(struct Node* root, int curr_height){
int index = curr_height;
if(ptr_to_table[index]){
ptr_to_table[index] -> next = root;
}
ptr_to_table[index] = root;
root -> next = NULL;
}
struct Node * helper(struct Node * root, int curr_height){
if(!root && !have_height){
ptr_to_table = allocate_direct_table(curr_height+9);
have_height = 1;
return NULL;
}
if(!root) return NULL;
curr_height ++;
helper(root -> left, curr_height);
curr_height --;
process(root, curr_height);
curr_height ++;
helper(root -> right, curr_height);
curr_height --;
return root;
}
struct Node* connect(struct Node* root) {
return helper(root, 0);
}
I still have no idea why +9
is needed for allocation.