在目标包含源的地方使用 strncpy()

Using strncpy() where destination contains the source

我写了一个函数来从 C 中的字符串中 trim 白色 space 字符。 我关心的是下面 trim() 函数的最后一行,其中源包含在目标中。测试用例以及其他一些测试结果都很好。复制源和目标位于同一内存中的全部或部分字符串会导致奇怪的问题吗?

源代码:

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

void trim(char *line)
  {
  int  i, len = strlen(line);
  char *ptr, whitespace[] = " \t\n";

  // scan for first char which does not match a char in whitespace string
  for (i=0; i<len; i++)
    if (strchr(whitespace, line[i]) == NULL)
      break;
  ptr = line + i;

  // scan for last char which does not match a char in whitespace string
  for (i=len; i>0; i--)
    if (strchr(whitespace, line[i]) == NULL)
      break;
  line[i] + 1) = '[=10=]';

  // copy result to line (this is the line relevant to the question)
  strncpy(line, ptr, len);
  }

int main(void)
  {
  int i;
  char test[4][64] = {
    "a line with no leading and trailing spaces",
    "  a line with some leading and trailing spaces  ",
    "\ta line with leading and trailing tabs\t",
    "\na line with leading and trailing newlines\n"
    };

  for (i=0; i<4; i++)
    {
    printf("test %d\nno trim: %s\n", i, test[i]);
    trim(test[i]);
    printf("trimmed: %s\n", test[i]);
    }
  return 0;
  }

如果您阅读例如this strncpy reference你会看到

The behavior is undefined if the character arrays overlap.

您需要使用 memmove 来代替,它指定用于处理重叠内存。

首先,第二个循环是错误的。我将其复制到此处以显示失败的确切位置:

// scan for last char which does not match a char in whitespace string
for (i=len; i>0; i--)
  if (strchr(whitespace, *(line + i)) == NULL)
    break;
*(line + i + 1) = '[=10=]';

两者之一:

  • 或者将 for 循环重写为 for(i = len-1; i>=0; i--),
  • 或者您在循环中写入对 *(line + i - 1).
  • 的引用

第一次进入循环时,你得到一个[=14=]字符(*(line + len)处的字符,它不在你使用的"\n \t"集合中,所以循环总是一开始就失败,让你在 i + 1 位置写一个 [=14=] (这是未定义的行为,因为你把它写在 [=14=] 字符之后)。

不鼓励在重叠字符串上使用 strncpy,正如其他回复所指出的那样。

备注

*(line + i - 1)等价于line[i-1],可读性更好,更不容易出错。并且与您在函数头中使用的指针定义完全兼容。 C 将两个表达式定义为等价的。

另一方面,我不知道搜索字符 '[=24=]' 的空终止字符串(使用 strchr(3))是否是未定义的行为,但如果它正确找到字符串终止符,你会很幸运并且不会脱离 for 循环([=14=] 以某种方式存在于所有字符串中)由于手册没有说明任何内容,也许有人可以从中说明标准。