c - Why does a GDB watchpoint stop on an irrelevant line when swapping binary tree nodes? -
i trying swap 2 nodes a
, b
in binary tree places stored in memory change tree topology not changed. added special handling swapping node parent, still seems doesn't work. i'm using valgrind vgdb can catch memory errors , consistent addresses. if have tree like
78 \ 40 / \ 5c c5
and try swap a=40
, b=5c
, links messed up. specifically, 40->right
. setting watchpoint on (watch -l
), found 40->right
being set 5c->right
(null
) memcpy
should be, being changed a
later if(a_l.left == b){
impossible. i've had watchpoint report wrong line before when using movq
instead of movb
in assembly, i'm pretty sure have sizes right time because didn't @ first , didn't make through swaps fixed , makes through around dozen. sanity check tree after every operation error here. here simplest demonstration manage:
#include <stdlib.h> #include <string.h> #include <assert.h> typedef struct avl_node avl_node; struct avl_node{ avl_node *left, *right, *parent; signed char balance; char data[]; }; avl_node *avl_root(avl_node *n){ while(n && n->parent){ n = n->parent; } return n; } inline static int avl_check_links(avl_node *n){ if(!n)return 1; if(n->left){ if(n->left->parent != n){ return 0; } if(!avl_check_links(n->left)){ return 0; } } if(n->right){ if(n->right->parent != n){ return 0; } if(!avl_check_links(n->right)){ return 0; } } return 1; } void avl_swap_nodes(avl_node *a, avl_node *b, size_t size){ avl_node a_l = *a, b_l = *b; char tmp[sizeof(avl_node) + size]; memcpy(tmp, a, sizeof(avl_node) + size); memcpy(a, b, sizeof(avl_node) + size); memcpy(b, tmp, sizeof(avl_node) + size); if(a_l.left){ a_l.left->parent = b; } if(a_l.right){ a_l.right->parent = b; } if(b_l.left){ b_l.left->parent = a; } if(b_l.right){ b_l.right->parent = a; } if(a_l.parent){ if(a_l.parent->left == a){ a_l.parent->left = b; }else{ a_l.parent->right = b; } } if(b_l.parent){ if(b_l.parent->left == b){ b_l.parent->left = a; }else{ b_l.parent->right = a; } } if(a_l.parent == b){ if(b_l.left == a){ b->left = a_l.left; a->left = b; }else{ b->right = a_l.right; a->right = b; } a->parent = b_l.parent; b->parent = a; }else if(b_l.parent == a){//gdb stops here on watch -l a->right if(a_l.left == b){ a->left = b_l.left; b->left = a; }else{ a->right = b_l.right; b->right = a; } b->parent = a_l.parent; a->parent = b; } assert(avl_check_links(avl_root(a))); assert(avl_check_links(avl_root(b))); } int main(void){ avl_node a, b, c, d; = (avl_node){.right=&b}; b = (avl_node){.left=&c, .right=&d, .parent=&a}; c = (avl_node){.parent=&b}; d = (avl_node){.parent=&b}; assert(avl_check_links(avl_root(&a))); avl_swap_nodes(&b, &c, 0); }
why gdb stop on wrong line? think may have fact using vgdb: skips lines when single step. why a->right
changed second time @ all? thank you.
you can file run reasonably recent versions of gcc, gdb, , valgrind doing gcc -g -o main main.c
, valgrind --vgdb=yes --vgdb-error=0 ./main&
, gdb main
, tar rem | vgdb
, b avl_swap_nodes
, c
, watch -l a->right
, , rid of vgdb process neatly doing c
repeatedly , ctrl-d
or kill
, ctrl-d
.
i figured out , isn't fun i'm going answer own question. node swapping code wrong. here version works
#include <stddef.h> void avl_swap_nodes(avl_node *a, avl_node *b, size_t size){ avl_node a_l = *a, b_l = *b; char tmp[offsetof(avl_node, data) + size]; memcpy(tmp, a, offsetof(avl_node, data) + size); memcpy(a, b, offsetof(avl_node, data) + size); memcpy(b, tmp, offsetof(avl_node, data) + size); if(a_l.parent == b){ if(b_l.left == a){ a->left = b; }else{ a->right = b; } b->parent = a; if(a->parent){ if(a->parent->left == b){ a->parent->left = a; }else{ a->parent->right = a; } } }else if(b_l.parent == a){ if(a_l.left == b){ b->left = a; }else{ b->right = a; } a->parent = b; if(b->parent){ if(b->parent->left == a){ b->parent->left = b; }else{ b->parent->right = b; } } }else{ if(a->parent){ if(b->parent == a->parent){ if(a->parent->left == b){ a->parent->left = a; b->parent->right = b; }else{ a->parent->right = a; b->parent->left = b; } }else{ if(a->parent->left == b){ a->parent->left = a; }else{ a->parent->right = a; } } } if(b->parent && b->parent != a->parent){ if(b->parent->left == a){ b->parent->left = b; }else{ b->parent->right = b; } } } if(a->left){ a->left->parent = a; } if(a->right){ a->right->parent = a; } if(b->left){ b->left->parent = b; } if(b->right){ b->right->parent = b; } assert_all(avl_root(a)); assert_all(avl_root(b)); }
the reason why gdb reporting watchpoint on wrong line because previous memory write overflows. can happen example when use movq
instead of movb
in assembly, or when char a; ((int*)&a) = (int)0;
in c, or when memcpy
more meant to. last 1 causing problems in code. consider struct struct a{int a; char b[];);
. sizeof(struct a)
8 because of structure padding, offsetof(struct a, b)
4. therefore if calculate size of struct a
data in flexible array @ end adding data size sizeof(struct a)
, calculate value 4 bytes greater should be. solution use offsetof(struct a, b);
instead.
the reason why gdb skipping lines because using valgrind --vgdb=yes
instead of valgrind --vgdb=full
.
Comments
Post a Comment