【问题标题】:Undefined behaviour whist concatenating strings in C在 C 中连接字符串的未定义行为
【发布时间】:2020-12-11 03:57:19
【问题描述】:

我正在编写一个 C 程序,它将连接所有行(包括“\n”),同时将指针保存到最终字符串的最后一个字符。但是,我没有得到我预期的结果。可能是什么问题?

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

struct Node {
  struct Node *next;
  struct Node *prev;
};

struct Node *CreateNewNode() {
  struct Node *newNode = malloc(sizeof(struct Node));
  return newNode;
}

struct PieceTable {
  char *buffer;
  char *ptr_to_last_character;
} PT;

void strconcatenate(char *source) {
  size_t source_len = strlen(source);
  size_t buffer_len = strlen(PT.buffer);
  PT.buffer = realloc(PT.buffer, buffer_len + source_len + 1);
  while (*source)
    *PT.ptr_to_last_character++ = *source++;
  *PT.ptr_to_last_character = '\0';
}

int main(int argc, char *argv[]) {

  char input_line[1024];

  PT.buffer = malloc(sizeof(char) * 2);
  *PT.buffer = '\0';
  PT.ptr_to_last_character = PT.buffer;

  struct Node *new_node = CreateNewNode();
  new_node->next = NULL;
  new_node->prev = NULL;

  strconcatenate("Lorem ipsum\n");
  strconcatenate("dolor sit amet\n");
  strconcatenate("consectetur adipiscing elit\n");

  printf("%s", PT.buffer);

  return 0;
}

预期输出:

Lorem ipsum
dolor sit amet
consectetur adipiscing elit

输出:

etur adipiscing elit

【问题讨论】:

  • “我没有得到预期的结果。” - 你期待什么?请编辑问题以明确说明。
  • @marko - 看看帖子底部
  • @Jonathan S. 这是什么代码 struct Node *new_node = CreateNewNode();新节点->下一个 = NULL; new_node->prev = NULL;在节目中做什么?
  • realloc 使指针无效,因为它分配新的存储空间并将内容移到那里。
  • @JonathanS。保持指向终止零的指针没有任何意义,因为在任何情况下您都需要调用 strlen。所以整个功能没有意义。

标签: c pointers string-concatenation c-strings function-definition


【解决方案1】:

对于初学者来说,不清楚为什么在这个语句中分配了两个字节而不是一个字节。

PT.buffer = malloc(sizeof(char) * 2);
*PT.buffer = '\0';

函数内this语句

size_t buffer_len = strlen(PT.buffer);

考虑到您已经有一个指向终止零的指针,这没有意义。所以它只是效率低下。

拨打realloc之后

PT.buffer = realloc(PT.buffer, buffer_len + source_len + 1);

指针PT.ptr_to_last_character 可能无效。

您还应该检查内存分配是否成功。否则指针PT.buffer可以设置为NULL以防万一失败。

该功能可以通过以下方式实现,如下面的演示程序所示。

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

struct PieceTable {
  char *buffer;
  char *ptr_to_last_character;
} PT;

int strconcatenate( const char *source ) 
{
    int success = 1;
    
    size_t n = PT.ptr_to_last_character - PT.buffer;
    
    size_t source_len = strlen( source );
    
    if ( source_len != 0 )
    {
        char *tmp = realloc( PT.buffer, n + source_len + 1 );
        
        if ( ( success = tmp != NULL ) )
        {
            PT.buffer = tmp;
            PT.ptr_to_last_character = PT.buffer + n;

            while ( ( *PT.ptr_to_last_character = *source++ ) ) ++PT.ptr_to_last_character;
        }
    }
    
    return success;
}   

int main(void) 
{
    PT.buffer = malloc( sizeof(char) );
    *PT.buffer = '\0';
    PT.ptr_to_last_character = PT.buffer;
    
    strconcatenate( "Lorem ipsum\n" );
    strconcatenate( "dolor sit amet\n" );
    strconcatenate( "consectetur adipiscing elit\n" );

    puts( PT.buffer );
    
    return 0;
}

程序输出是

Lorem ipsum
dolor sit amet
consectetur adipiscing elit

还要考虑到不是这个手动编写的循环

while ( ( *PT.ptr_to_last_character = *source++ ) ) ++PT.ptr_to_last_character;

您可以使用标准的 C 字符串函数 strcatstrcpy,这样效率会更高。

在这种情况下,您的功能可以如下所示

int strconcatenate( const char *source ) 
{
    int success = 1;
    
    size_t n = PT.ptr_to_last_character - PT.buffer;
    
    size_t source_len = strlen( source );
    
    if ( source_len != 0 )
    {
        char *tmp = realloc( PT.buffer, n + source_len + 1 );
        
        if ( ( success = tmp != NULL ) )
        {
            PT.buffer = tmp;
            PT.ptr_to_last_character = PT.buffer + n;

            strcpy( PT.ptr_to_last_character, source );
            
            PT.ptr_to_last_character += source_len;
        }
    }
    
    return success;
}   

【讨论】:

    【解决方案2】:

    警告: Paul 已经发现了问题,但是既然你说你需要一个指针......

    您可以通过保存/恢复偏移量来调整“最后一个”指针:

    ptrdiff_t offset = PT.ptr_to_last_character - PT.buffer;
    PT.buffer = realloc(PT.buffer, buffer_len + source_len + 1);
    PT.ptr_to_last_character = PT.buffer + offset;
    

    我在下面的完整代码中注释了其他一些清理问题。 #if 0 包装旧/原始代码。 #if 1 包装新的/重构的代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stddef.h>
    
    struct Node {
        struct Node *next;
        struct Node *prev;
    };
    
    struct Node *
    CreateNewNode()
    {
        struct Node *newNode = malloc(sizeof(struct Node));
    
        // NOTE/WARN: this was moved from main to be more general
    #if 1
        newNode->next = NULL;
        newNode->prev = NULL;
    #endif
    
        return newNode;
    }
    
    struct PieceTable {
        char *buffer;
        char *ptr_to_last_character;
    } PT;
    
    void
    strconcatenate(char *source)
    {
        size_t source_len = strlen(source);
        size_t buffer_len = strlen(PT.buffer);
    
        // NOTE/BUG: after the realloc, PT.buffer can change but
        // PT.ptr_to_last_character is pointing within the _old_ buffer
        // we need to recalc using an offset
    #if 0
        PT.buffer = realloc(PT.buffer, buffer_len + source_len + 1);
    #else
        ptrdiff_t offset = PT.ptr_to_last_character - PT.buffer;
        PT.buffer = realloc(PT.buffer, buffer_len + source_len + 1);
        PT.ptr_to_last_character = PT.buffer + offset;
    #endif
    
        while (*source)
            *PT.ptr_to_last_character++ = *source++;
    
        *PT.ptr_to_last_character = '\0';
    }
    
    int
    main(int argc, char *argv[])
    {
    
        char input_line[1024];
    
        // NOTE/BUG: sizeof(char) is _always_ 1 by definition
    #if 0
        PT.buffer = malloc(sizeof(char) * 2);
    #else
        PT.buffer = malloc(2);
    #endif
        *PT.buffer = '\0';
        PT.ptr_to_last_character = PT.buffer;
    
        // NOTE/WARN: this isn't used anywhere
        struct Node *new_node = CreateNewNode();
    
        // NOTE/WARN: this would be better inside CreateNewNode
    #if 0
        new_node->next = NULL;
        new_node->prev = NULL;
    #endif
    
        strconcatenate("Lorem ipsum\n");
        strconcatenate("dolor sit amet\n");
        strconcatenate("consectetur adipiscing elit\n");
    
        printf("%s", PT.buffer);
    
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      你重新分配了PT.buffer,但你没有更新PT.ptr_to_last_character。当realloc 不能只是扩展当前分配而是在不同地址返回一个更大的新区域时,这会导致未定义的行为,从而使ptr_to_last_character 指向旧内存。

      可能更好的是存储缓冲区的长度而不是指向最后一个字符的指针,这样您就不必担心它会失效。

      【讨论】:

      • 谢谢,我实际上需要最后一个字符来进行优化。我已经摸索了一段时间,但仍然没有找到合适的解决方法。
      • @jonathan 只是存储计数,然后没有什么可以更新
      • @AnttiHaapala 我实际上需要 pointer 指向最后一个字符,否则,我会实现一个 strcat
      • @JonathanS - char *buffer, size_t count 的空间要求通常与您的结构相同,但数学计算要容易一些。您可以随时恢复指向最后一个字符或一个超出的指针。
      猜你喜欢
      • 2020-10-15
      • 1970-01-01
      • 1970-01-01
      • 2013-09-05
      • 2017-08-31
      • 1970-01-01
      • 1970-01-01
      • 2012-05-30
      • 2015-04-14
      相关资源
      最近更新 更多