std::string的strncpy等价函数是什么?

7
在C++标准库中是否有与strncpy完全等效的函数?我的意思是一种函数,它可以将一个字符串从一个缓冲区复制到另一个缓冲区,直到遇到终止符0为止。例如,当我需要从不安全的源(如TCP数据包)解析字符串时,我能够在复制数据时检查长度。
我已经对这个问题进行了很多搜索,并且我也发现了一些有趣的主题,但所有这些人都对std :: string :: assign感到满意,该函数也能够将要复制的字符数作为参数。我对这个函数的问题是,它不会执行任何检查,以确定是否已经遇到了终止null-它认真地采取给定的大小并像memcpy一样将数据复制到字符串的缓冲区中。这样就分配和复制了比必须完成的更多的内存。
这是我目前解决这个问题的方式,但是有一些额外的开销,我希望避免:
    // Get RVA of export name
    const ExportDirectory_t *pED = (const ExportDirectory_t*)rva2ptr(exportRVA);
    sSRA nameSra = rva2sra(pED->Name);

    // Copy it into my buffer
    char *szExportName = new char[nameSra.numBytesToSectionsEnd];
    strncpy(szExportName, 
            nameSra.pSection->pRawData->constPtr<char>(nameSra.offset),
            nameSra.numBytesToSectionsEnd);
    szExportName[nameSra.numBytesToSectionsEnd - 1] = 0;

    m_exportName = szExportName;
    delete [] szExportName;

这段代码是我为PE二进制文件编写的解析器的一部分(确切地说是解析导出表的例程)。rva2sra将相对虚拟地址转换为PE节相对地址。 ExportDirectory_t结构包含二进制文件导出名称的RVA,这应该是以零结尾的字符串。但并不总是这样 - 如果有人想要省略终止零,则可能使我的程序运行到不属于该节的内存中,最终会崩溃(在最好的情况下...)。
实现这样一个函数自己也不是什么大问题,但我更喜欢C++标准库中已经实现了这个功能的解决方案。

7
std::string 不以 null 字符结尾。您是想要字符串的一个副本,还是只需要一个副本,包括第一个 null 字节之前的所有内容(可能存在或不存在)? - Seth Carnegie
哦,好的,我想复制到第一个空字节为止的所有内容。 - athre0z
1
小心你所要求的。 - Keith Thompson
7个回答

12

如果您知道想要将其转化为 string 的缓冲区中至少包含一个 NUL,则可以直接将其传递给构造函数:

const char[] buffer = "hello\0there";

std::string s(buffer);

// s contains "hello"

如果你不确定,那么你只需要搜索字符串中的第一个空值,并告诉 string 构造函数复制那么多数据:

int len_of_buffer = something;
const char* buffer = somethingelse;

const char* copyupto = std::find(buffer, buffer + len_of_buffer, 0); // find the first NUL

std::string s(buffer, copyupto);

// s now contains all the characters up to the first NUL from buffer, or if there
// was no NUL, it contains the entire contents of buffer

你可以将第二个版本(即使缓冲区中没有NUL字符,它也始终有效)封装成一个整洁的小函数:

std::string string_ncopy(const char* buffer, std::size_t buffer_size) {
    const char* copyupto = std::find(buffer, buffer + buffer_size, 0);

    return std::string(buffer, copyupto);
}

需要注意的一点是:如果你直接将一个const char*传递给单参数构造函数,它会一直寻找NUL。使用std::string的单参数构造函数时,重要的是要知道缓冲区中至少有一个NUL。

不幸(或者幸运)的是,对于std::string,没有内置的完全等效于strncpy的函数。


1
这种写法 std::string(buffer, strnlen(buffer, buffer_size)) 对于同样的功能来说,是否是一个有用的简写形式? - Guss

3
在STL中,std::string类可以在字符串内部包含空字符("xxx\0yyy"是长度为7的有效字符串)。这意味着它对空终止符一无所知(好吧,几乎没有,有与C字符串之间的转换)。换句话说,在STL中没有strncpy的替代方案。
有几种方法可以使用更短的代码仍然实现您的目标:
const char *ptr = nameSra.pSection->pRawData->constPtr<char>(nameSra.offset);
m_exportName.assign(ptr, strnlen(ptr, nameSra.numBytesToSectionsEnd));

或者

const char *ptr = nameSra.pSection->pRawData->constPtr<char>(nameSra.offset);
m_exportName.reserve(nameSra.numBytesToSectionsEnd);
for (int i = 0; i < nameSra.numBytesToSectionsEnd && ptr[i]; i++) 
  m_exportName += ptr[i];

3

在C++标准库中,是否有与strncpy完全等效的函数?

我当然希望没有!

我的意思是,是否有一个函数,将字符串从一个缓冲区复制到另一个缓冲区,直到遇到终止符0为止?

啊,但这不是strncpy()所做的全部--或者至少它不是全部。

strncpy()允许您指定目标缓冲区的大小n,并且最多复制n个字符。就这样很好。如果源字符串的长度(“长度”定义为终止'\0'之前的字符数)超过了n,则目标缓冲区将填充其他的\0',这很少有用。如果源字符串的长度超过了n,那么终止的'\0'就不会被复制

strncpy() 函数是为早期Unix系统中目录项中存储文件名的方式而设计的:它是一个14字节固定大小缓冲区,可以容纳最多14个字符的名称。(编辑:我不确定这是否是其设计的实际动机。) 它可以说不是一个字符串函数,也不仅仅是 strcpy() 的“更安全”的变体。
您可以使用 strncat() 来实现与人们可能假设 strncpy() 所做的相当的操作(考虑到名称)。
char dest[SOME_SIZE];
dest[0] = '\0';
strncat(dest, source_string, SOME_SIZE);

这将始终以'\0'结尾目标缓冲区,并且不会不必要地填充额外的'\0'字节。
你真的在寻找那个的std::string等效物吗?
编辑:在我写完上面的内容之后,我在我的博客上发布了this rant

我知道这些事实(除了关于零填充的事情),但它是我能找到的标准C/C++库中最好的函数来满足我的需求。正如您在第一篇帖子中看到的示例中所示,我手动添加了终止空字节。strncat不会搜索目标字符串中的第一个空字节,然后从找到的空字节位置开始附加,同时检查是否超过长度,并最终添加终止空字节吗?因此,我必须手动将目标缓冲区的第一个字节设置为null,对吗? - athre0z

1
字符串的子串构造函数可以实现你所需的功能,虽然它不是strncpy的完全等价物(请参见我的注释):
std::string( const std::string& other, 
          size_type pos, 
          size_type count = std::string::npos,
          const Allocator& alloc = Allocator() );

构造一个由其他字符串的子串[pos,pos+count)组成的字符串。 如果count == npos或者请求的子串持续到字符串的末尾,则生成的子串为[pos,size())。

来源:http://www.cplusplus.com/reference/string/string/string/

示例:

#include <iostream>
#include <string>
#include <cstring>
int main ()
{
    std::string s0 ("Initial string");
    std::string s1 (s0, 0, 40); // count is bigger than s0's length
    std::string s2 (40, 'a');   // the 'a' characters will be overwritten
    strncpy(&s2[0], s0.c_str(), s2.size());
    std::cout << "s1: '" << s1 << "' (size=" << s1.size() << ")" << std::endl;
    std::cout << "s2: '" << s2 << "' (size=" << s2.size() << ")" << std::endl;
    return 0;
}

输出:

s1: 'Initial string' (size=14)
s2: 'Initial string' (size=40)

与strncpy的区别:

  • 字符串构造函数总是将空终止字符附加到结果中,而strncpy则不会;
  • 如果在请求的计数之前达到空终止字符,则字符串构造函数不会用0填充结果,而strncpy会。

1

没有内置的等价物。您需要自行编写strncpy

#include <cstring>
#include <string>

std::string strncpy(const char* str, const size_t n)
{
    if (str == NULL || n == 0)
    {
        return std::string();
    }

    return std::string(str, std::min(std::strlen(str), n));
}

0

3
OP不是在寻找strcpy,而是在寻找strncpy - pmr
@pmr:OP澄清了他只需要到第一个NULL字节。 - Mooing Duck
不防止读取超出缓冲区已知边界的内容。 - Jetski S-type

0

std::string有一个构造函数,其下一个签名可用:

string ( const char * s, size_t n );

具体描述如下:

将内容初始化为由指向 s 的字符数组中前 n 个字符形成的字符串的副本。


请注意,这不会在空字节处停止(它说的是“字符数组”,而不是“C 字符串”)。这可能是您想要的,也可能不是。 - Seth Carnegie
2
谢谢您的回复,但这并不是我的问题想要知道的确切答案。构造函数会复制所有内容,直到遇到第一个空字节或(如果指定了大小)将所有内容都复制,就像memcpy一样。如果我的源字符串中有一个空字节,则它后面的内容也会被复制。 - athre0z
@SethCarnegie 是的,这就是为什么我添加了描述。也许这个构造函数不是 OP 需要的。 - BЈовић
这个构造函数不尊重空终止符,对我来说是相当具有欺骗性的。使用:const char arr[10] = "Hello"; std::string a(arr); std::string b(arr, 10); a != b,因为b在末尾包含4个未初始化的字节以达到长度10。很遗憾std::strings可以在其中包含\0字符,在大多数情况下这是没有意义的。 - Jetski S-type

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