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++ 标准库中实现了这个功能,我会更喜欢它。

4

7 回答 7

11

如果您知道要制作的缓冲区string中至少有一个 NUL,那么您可以将其传递给构造函数:

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

std::string s(buffer);

// s contains "hello"

如果你不确定,那么你只需要在字符串中搜索第一个 null,然后告诉构造函数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);
}

但需要注意的一点是:如果您将单参数构造函数 aconst char*自己交给它,它将一直运行,直到找到 NUL。如果您使用std::string.

不幸的是(或幸运的是),没有内置的完美等效strncpyfor std::string

于 2012-01-16T19:46:57.537 回答
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];
于 2012-01-16T19:50:25.937 回答
3

C++ 标准库中是否有与 strncpy完全等价的功能?

我当然希望不会!

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

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

strncpy()允许您指定n目标缓冲区的大小 , 并最多复制n字符。就目前而言,这很好。如果源字符串的长度(“长度”定义为终止符之前的字符数'\0')超过n,则目标缓冲区会用额外的 s 填充\0',这很少有用。如果源字符串超过长度n则不复制终止'\0'

strncpy()函数是为早期 Unix 系统在目录条目中存储文件名的方式设计的:作为一个 14 字节的固定大小缓冲区,最多可以容纳 14 个字符的名称。 (编辑:我不能 100% 确定这是其设计的实际动机。)它可以说不是字符串函数,也不仅仅是strcpy().

您可以使用以下方法实现与您可能假设strncpy()的功能(给定名称)相同的功能strncat()

char dest[SOME_SIZE];
dest[0] = '\0';
strncat(dest, source_string, SOME_SIZE);

这将始终'\0'终止目标缓冲区,并且不会不必要地用额外的'\0'字节填充它。

你真的在寻找std::string类似的东西吗?

编辑:在我写完以上内容后,我在我的博客上发布了这个咆哮。

于 2012-01-16T20:14:43.287 回答
1

std::string有一个可以使用的带有下一个签名的构造函数:

string ( const char * s, size_t n );

下一个描述:

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

于 2012-01-16T19:39:19.733 回答
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));
}
于 2014-08-06T05:59:40.547 回答
1

字符串的子字符串构造函数可以做你想做的事,虽然它不完全等同于 strncpy (见我的笔记在最后):

std::string( const std::string& other, 
          size_type pos, 
          size_type count = std::string::npos,
          const Allocator& alloc = Allocator() );

用 other 的子字符串 [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 会。
于 2016-09-27T01:05:35.717 回答
0

使用类的构造函数:

string::string str1("Hello world!");
string::string str2(str1);

根据本文档,这将产生一个精确的副本:http ://www.cplusplus.com/reference/string/string/string/

于 2012-01-16T19:32:27.890 回答