带有多个分隔符的 Strsep:奇怪的结果

问题描述

我目前在使用带有多个分隔符的 strsep 时遇到了一些奇怪的结果。我的分隔符包括 TAB 字符、空格字符以及 ><

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

int main()
{
    char buffer[50];
    char *curr_str = NULL;
    const char delim[4] = "\t >";
    //const char delim[4] = "\t ><"; // This does not work
  
    snprintf(buffer,50,"%s","echo Hello");
  
    char *str_ptr = buffer;
  
    curr_str = strsep(&str_ptr,delim);
  
    if (curr_str != NULL)
        printf("%s\n",curr_str);

    curr_str = strsep(&str_ptr,delim);
    if (curr_str != NULL)
        printf("%s\n",curr_str);
    return (0);
}

这个输出正是我所期望的。

echo 
Hello

但是,只要我为分隔符添加

cho

不知何故,第一个字符被截断了。发生这种情况是否有原因?

谢谢。

解决方法

strsep 的第二个参数,delim 是一个空终止字符串(就像 C 中的所有字符串一样),所以你必须为终止字符留出空间:

const char delim[5] = "\t ><"; // This does work
//const char delim[] = "\t ><"; // or this

如果你不结束字符串,它会通过数组探索内存并找到许多新的分隔字符来使用,这就是你的情况。

,

“...第一个字符被截断。发生这种情况的背后有什么原因吗?”

是的,未定义行为是由在 C 字符串函数中使用的非空终止字符数组引起的。

如果填充的 const char delim[4] 不包含空终止符,它将只是一个 char 数组,而不是一个 C string。它可能会也可能不会表现出奇怪行为,但如果与任何 undefined behavior(例如 {{1} }.

curr_str = strsep(&str_ptr,delim);

有 4 个字符的空间。

const char delim[4];

可以在内存中这样概念化:

"\t ><"  //contains exactly 4 char

它应该包含以下内容:

|\t| |>|<|?|?|?|  // ? = unknown content,possibly no null termination
         ^end of owned memory

在声明中需要更多空间,例如以下两个选项之一:

|\t| |>|<|\0|?|?|  // null termination  
            ^end of owned memory (5 char wide)

const char delim[5] = "\t ><";
,

const char delim[4] = "\t ><"; 没有定义正确的 C 字符串,因为空终止符没有空格。因此,内存中 delim 之后的任何非零字节都将成为分隔符字符串的一部分。

这当然是未定义的行为,在您的情况下,编译器可能会在没有任何填充的情况下将 delim 定位在 buffer 之前,有效地继续使用所有的分隔符序列字符串 "echo Hello" 中的字符。这会导致对 strsep 的第一次调用返回一个空字符串。

您可以检查此 Godbolt instance 是否在 32 位模式下确实如此,但在 64 位模式下则不然(删除 -m32 编译器选项)。

这个问题很容易解决。您可以让编译器确定 delim 数组的长度:

const char delim[] = "\t ><";

或者你可以使用指向字符串常量的指针:

const char *delim = "\t ><";