Memcpy、字符串和终止符

8

我需要编写一个函数,将字符串的内容填充到已分配长度的char*缓冲区中。如果字符串太长,我只需要截断它。缓冲区不是由我分配的,而是由我的函数用户分配的。我尝试了以下代码:

int writebuff(char* buffer, int length){
    string text="123456789012345";
    memcpy(buffer, text.c_str(),length);
    //buffer[length]='\0';
    return 1;
}


int main(){
    char* buffer = new char[10];
    writebuff(buffer,10);
    cout << "After: "<<buffer<<endl;
}

我的问题是关于终止符:应该存在还是不存在?这个函数在更广泛的代码中使用,有时当字符串需要被截断时,我似乎会遇到奇怪字符的问题。请给出正确的操作建议。

2
如果你使用的是C++字符串,请使用string而不是char*,并使用copy而不是memcpy。 - DumbCoder
如果你想编写一个多语言源文件,应该避免使用“C++isms”:不要使用 std::new<<(除非你是指位移),等等。 - pmg
1
writebuff 应该执行其所宣传的功能。如果调用者期望有终止符,则 writebuff 必须提供它。如果调用者不期望有终止符,则 writebuff 不得提供它。在这种特定情况下,调用者明显期望有终止符(operator<<(ostream, char*) 期望有终止符)。 - Robᵩ
这基本上就是 strlcpy(3) 函数,或者我有什么地方理解错了吗? - Mel
10个回答

13

一个 C 风格的字符串必须以零字符'\0'结尾。

此外,您的代码还有另一个问题——它可能尝试从源字符串的末尾之后进行复制,这是典型的未定义行为。看起来它能工作,直到有一次字符串分配在堆内存块的末尾,而复制会跑到受保护的内存区域并惨遭失败。您应该仅复制到缓冲区长度和字符串长度的最小值

P.S. 以下是您函数的一个好版本。感谢Naveen指出了您终止 null 的偏差错误。我已经利用了您的返回值来指示返回字符串的长度或者如果传入的长度小于等于 0,则表示所需的字符数。

int writebuff(char* buffer, int length)
{
    string text="123456789012345";
    if (length <= 0)
        return text.size();
    if (text.size() < length)
    {
        memcpy(buffer, text.c_str(), text.size()+1);
        return text.size();
    }
    memcpy(buffer, text.c_str(), length-1);
    buffer[length-1]='\0';
    return length-1;
}

8

如果你想把缓冲区当作字符串处理,你需要在结尾添加空字符(NULL)。为此,你需要使用 memcpy 复制 length-1 个字符,并将第 length-1 个字符设置为 \0


相反地,如果您不想将缓冲区视为字符串,则不应该以NUL结尾。 - Robᵩ
拷贝length个字符而不是length-1个是可以的,这样可以避免当你传递缓冲区长度为0时产生的一个错误。 - Mark Ransom

2

看起来你在使用C++ - 鉴于此,最简单的方法是(假设接口规范要求NUL终止):

int writebuff(char* buffer, int length)
{
  string text = "123456789012345";
  std::fill_n(buffer, length, 0); // reset the entire buffer
  // use the built-in copy method from std::string, it will decide what's best.
  text.copy(buffer, length);
  // only over-write the last character if source is greater than length
  if (length < text.size())
    buffer[length-1] = 0;
  return 1; // eh?
}

1

除非您在明确地将其长度随处传递并声明缓冲区未以空字符结尾,否则 char * Buffers 必须以空字符结尾。


0

它应该绝对存在,这可以防止太长的字符串填满缓冲区并在以后访问时导致溢出。尽管在我看来,strncpy 应该代替 memcpy,但你仍然需要将其空终止。(另外,你的示例泄漏了内存)。

*如果你有任何疑问,请选择最安全的路线!


0
我的问题是关于终止符:它应该存在还是不存在?
是的,它应该存在。否则,您将如何知道字符串在哪里结束?cout也会如何知道呢?它会一直打印垃圾,直到遇到一个值恰好为\0的垃圾。您的程序甚至可能会崩溃。
另外,您的程序存在内存泄漏。它没有释放分配的内存。但由于您正在从main()退出,这并不重要;毕竟,一旦程序结束,所有内存都会返回给操作系统,无论您是否释放它。但总的来说,如果您不忘记自己释放内存(或任何其他资源),这是一个好习惯。

0

是否应该使用\0终止字符串取决于您的writebuff函数的规范。如果在调用函数后,buffer中的内容应该是有效的C风格字符串,则应该使用\0终止它。

请注意,c_str()将为您终止\0,因此您可以使用text.size() + 1作为源字符串的大小。还要注意,如果length大于字符串的大小,则会复制比当前代码提供的text更多的内容(您可以使用min(length - 2, text.size() + 1/*trailing \0*/)来防止这种情况,并设置buffer[length - 1] = 0来结束它)。

顺便说一下,在main中分配的buffer泄漏了。


0
  1. main() 函数中,你应该使用 delete 删除你用 new 分配的缓冲区,或者静态分配它(char buf[10])。是的,它只有 10 个字节,而且是一个内存“池”,不是泄漏,因为它是一次性分配的,而且是整个程序运行时间需要的内存。但这仍然是一个好习惯。

  2. 在 C/C++ 中,字符缓冲区的一般约定是它们以空字符结尾,所以除非我被明确告知不要这样做,否则我会包含它。如果我这样做了,我会加上注释,甚至可能使用 typedef 或名称来指示 char * 参数的结果是一个没有以空字符结尾的字符串。


0

我同意Necrolis的看法,使用strncpy是正确的选择,但如果字符串太长,它将无法获取空终止符。你在放置显式终止符方面有正确的想法,但是按照你的代码编写,它会将其放置在末尾之后一个位置。(这是C语言,因为你似乎更多地在使用C而不是C++?)

int writebuff(char* buffer, int length){
    char* text="123456789012345";
    strncpy(buffer, text, length);
    buffer[length-1]='\0';
   return 1;
}

0

首先,我不知道writerbuff是否应该终止字符串。这是一个设计问题,由决定writerbuff是否存在的人来回答。

其次,以您的具体示例为整体,存在两个问题。一是将未终止的字符串传递给operator<<(ostream, char*)。第二个是被注释掉的行写入了超出指定缓冲区的范围。这两个都会引起未定义的行为。

(第三个是设计缺陷--您能确定length始终小于text的长度吗?)

试试这个:

int writebuff(char* buffer, int length){
  string text="123456789012345";
  memcpy(buffer, text.c_str(),length);
  buffer[length-1]='\0';
  return 1;
}


int main(){
  char* buffer = new char[10];
  writebuff(buffer,10);
  cout << "After: "<<buffer<<endl;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接