【问题标题】:C program is taking a branch even though it shouldn'tC 程序正在采取一个分支,即使它不应该
【发布时间】:2018-09-09 14:46:45
【问题描述】:

我编写了一个从数组构造二叉搜索树的 C 程序。它经过以下步骤:

1:使用qsort()对数组进行排序。

2:使用递归函数treeify()将排序好的数组元素放入二叉树中:

2a:取数组的中间元素(将其长度除以 2)并将其作为树结构(此子树的根节点)的 content 字段。

2b:函数然后将剩余元素的左半部分和右半部分复制到较小的数组中,并分别为这些数组中的每一个调用自身。

2c:通过根节点返回树。

3:递归遍历树并以缩进格式打印其内容。

基本上,我使用分而治之的范式从已经排序的数组构建树。令人惊讶的是(因为这是我第一次设计 D&C 算法)这部分进行得相当顺利。

我真正遇到麻烦的地方是第 3 步。有时它有效,当它有效时,所有元素的顺序都正确,所以这部分显然有效。但是在我运行程序的 90% 的情况下,当它到达第一个叶节点时会出现段错误。

这是完整的程序文本。我已经更改了打印功能,以便它打印节点的地址(用于调试目的)。原来是显示数值的……

#include <stdio.h>
#include <stdlib.h>

struct tree {
    int content;
    struct tree *left;
    struct tree *right;
};

struct tree *treeify( int *, size_t );
void printtree( struct tree *, int );
int comp( int *, int * );

int main( int argc, char **argv ){
    int array[] = { 5, 6, 7, 2, 3, 4, 9, 1, 8, 0 };
    /* Sort array */
    qsort( (void *) array, 10, sizeof( int ), (int (*)(const void *, const void *)) &comp );
    for( int i = 0; i < 10; i++ ){
        printf( "%d ", array[i] );
    }
    printf( "\n" );
    /* Treeify array */
    struct tree *rootnode = treeify( array, 10 );
    /* Print tree */
    printtree( rootnode, 0 );
    return 0;
}

// Place sorted array elements in a tree
// Function is called for each subtree
struct tree *treeify( int *array, size_t size ){
    struct tree *root = (struct tree *) malloc( sizeof( struct tree ) );
    size_t middle = size/2;
    int leftsize = middle, rightsize = size-middle-1;
    int left[leftsize], right[rightsize];
    for( int i = 0; i < leftsize; i++ ) left[i] = array[i];
    for( int i = 0; i < rightsize; i++ ) right[i] = array[i+middle+1];
    root->content = array[middle];
    if( leftsize > 0 ) root->left = treeify( left, leftsize );
    if( rightsize > 0 ) root->right = treeify( right, rightsize );
    return root;
}

// Print tree contents in indented format
void printtree( struct tree *node, int level ){
    for( int i = 0; i < level; i++ ) printf( "  " );
    printf( "%x\n", &(node->content) );
    if( node->left ) printtree( node->left, level+1 );
    if( node->right ) printtree( node->right, level+1 );
}

// Comparison function for qsort
int comp( int *xp, int *yp ){
    int x = *xp, y = *yp;
    if( x < y ) return -1;
    if( x > y ) return 1;
    return 0;
}

我已经通过在遍历树时打印节点的地址来隔离问题。这是成功运行的输出:

0 1 2 3 4 5 6 7 8 9 
cbe00000
  cbe00020
    cbe00040
      cbe00060
    cbe00080
      cbe000a0
  cbe000c0
    cbe000e0
      cbe00100
    cbe00120

还有一次不成功的运行:

f04032b0
  f04032d0
    f04032f0
      f0403310
        0   
Segmentation fault: 11

请注意,成功的运行如何仅通过树的三个级别,然后返回并返回。不成功的运行经过四个级别,到达一个空指针。

具体来说,当程序到达这一行时:

        if( node->left ) printtree( node->left, level+1 );

尽管node-&gt;left 评估为零(如输出的第五行所示),但它仍采用分支。

这是我一生无法理解的。该条件显然被评估为假(我已经验证过),但程序仍在使用该分支,就好像它被评估为真(并且仅在大多数情况下,而不是所有情况下)。

这在我之前从未发生过。我需要一个比我更了解 C 的人来为我阐明这一点。

我能想到的唯一可能:

  • 一些古怪的编译器优化

  • 我在某处犯了一个愚蠢的单字符错误

  • 我的 CPU 部分烧坏了

【问题讨论】:

  • 两种行为是指一种称为未定义行为的行为。尝试运行valgrind下的代码,检查是否有无效读写。
  • 分配新的tree时,不设置left = right = NULL。
  • 是的,解决方案是简单地将left 和right 初始化为NULL,您的代码中没有单个NULL。
  • &amp;comp 前面的演员表表明您应该更改 comp 函数的原型以符合要求。

标签: c arrays segmentation-fault binary-tree binary-search-tree


【解决方案1】:

问题是您尝试从结构的未初始化成员中读取,第一次发生就在这里

if (node->left) 

在printtree() 函数中。

未初始化的值,保持不变,尝试读取它们是未定义的行为,这就是为什么您的程序并不总是表现相同的原因。

你需要初始化两个成员,事实上最好有

struct tree *create_node(int content)
{
    struct tree *node;
    node = malloc(sizeof(*node));
    if (node == NULL)
        return NULL;
    node->content = content;
    node->left = NULL;
    node->right = NULL;
    return node;
}

你也应该,

  1. 避免强制转换 malloc() 或任何返回 void * 的函数,因为讨论了 here。
  2. 在使用指针之前检查malloc() 没有返回NULL。

【讨论】:

  • 是的,现在说得通了。此外,我意识到我正在打印的值不是由if 语句评估的值。我正在打印&amp;(node-&gt;content),而条件是评估node。这就是为什么即使地址是NULL,它似乎也会选择分支的原因。
【解决方案2】:

对于初学者,函数comp 应声明为

int comp( const void *, const void * );

其次在函数treeify中,当leftsize或rightsize等于0时,数据成员right的数据成员left将具有不确定值。

不使用辅助数组,函数可以更简单的实现。

struct tree * treeify( const int *array, size_t size )
{
    struct tree *node = NULL;

    if ( size )
    {
        node = malloc( sizeof( struct tree ) );
        size_t middle = size / 2;

        node->content = array[middle];

        node->left = treeify( array, middle );
        node->right = treeify( array + middle + 1, size - middle - 1 );
    }

    return node;
}

函数printtree( 错误。例如,它不检查第一个参数是否等于 NULL。

这是一个演示程序

#include <stdio.h>
#include <stdlib.h>

struct tree 
{
    int content;
    struct tree *left;
    struct tree *right;
};

int comp( const void *, const void * );
struct tree * treeify( const int *, size_t );
void printtree( const struct tree *, int level );

int main(void) 
{
    int array[] = { 5, 6, 7, 2, 3, 4, 9, 1, 8, 0 };
    const size_t N = sizeof( array ) / sizeof( *array );

    qsort( array, N, sizeof( *array ), comp );

    for ( size_t i = 0; i < N; i++ ) printf( "%d ", array[i] );
    putchar( '\n' );

    struct tree *rootnode = treeify( array, N );

    printtree( rootnode, 0 );

    return 0;
}

int comp( const void *left, const void *right )
{
    int x = *( const int * )left;
    int y = *( const int * )right;

    return ( y < x ) - ( x < y );
}

struct tree * treeify( const int *array, size_t size )
{
    struct tree *node = NULL;

    if ( size )
    {
        node = malloc( sizeof( struct tree ) );
        size_t middle = size / 2;

        node->content = array[middle];

        node->left = treeify( array, middle );
        node->right = treeify( array + middle + 1, size - middle - 1 );
    }

    return node;
}

void printtree( const struct tree *node, int level )
{
    if ( node )
    {
        printf( "%*s", level, "" );
        printf( "%d\n", node->content );
        if( node->left ) printtree( node->left, level + 1 );
        if( node->right ) printtree( node->right, level + 1 );
    }
}

它的输出是

0 1 2 3 4 5 6 7 8 9 
5
 2
  1
   0
  4
   3
 8
  7
   6
  9

【讨论】:

  • 是的,使用指针算法比复制数组更有意义。出于某种原因,我认为仅适用于具有空终止符的字符串,但由于数组的长度是作为参数给出的,这应该足够了。
  • 我更喜欢你的comp() 功能。比if 子句优雅得多。
猜你喜欢
  • 2011-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-29
  • 1970-01-01
  • 2019-06-26
  • 1970-01-01
相关资源
最近更新 更多