【问题标题】:strncpy equivalent for std::string?strncpy 等效于 std::string?
【发布时间】:2012-01-16 19:29:18
【问题描述】:

在 C++ 标准库中有 exact 等效于 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++ 标准库中实现了解决方案,我会更喜欢它。

【问题讨论】:

  • std::strings 不是以空值结尾的。你想要一个字符串的副本,还是只复制直到第一个空字节(可能存在也可能不存在)的所有内容?
  • 哦,很高兴知道。我想将所有内容复制到第一个空字节。

标签: c++ string strncpy


【解决方案1】:

如果您知道要创建 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);
}

但有一点需要注意:如果你自己给单参数构造函数一个const char*,它会一直运行,直到找到一个NUL。如果使用 std::string 的单参数构造函数,请务必知道缓冲区中至少有一个 NUL。

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

【讨论】:

  • 谢谢,这正是我想要的。
【解决方案2】:

STL 中的std::string 类可以在字符串中包含空字符("xxx\0yyy" 是长度为 7 的完全有效的字符串)。这意味着它对空终止一无所知(几乎,有从/到 C 字符串的转换)。换句话说,strncpy 在 STL 中没有替代方案。

仍然有几种方法可以使用更短的代码来实现您的目标:

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's,这很少有用。如果源字符串的长度超过n,则不会复制终止的'\0'

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

    您可以使用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,对吗?
    【解决方案4】:

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

    string ( const char * s, size_t n );
    

    有下一个描述:

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

    【讨论】:

    • 请注意,这不会在空字节处停止(它说“字符数组”,而不是“C 字符串”)。这可能是也可能不是你想要的。
    • 感谢您的回复,但这并不是我想知道的。构造函数复制所有内容,直到遇到第一个空字节,或者,如果给定大小,则复制所有内容,就像 memcpy 一样。如果我的源字符串中有一个空字节,它后面的内容也会被复制。
    • @SethCarnegie 是的,这就是我添加描述的原因。也许这个构造函数不是 OP 需要的
    • 这不尊重空终止符,对我来说是一个相当具有欺骗性的构造函数。使用: const char arr[10] = "Hello"; std::string a(arr); std::string b(arr, 10); a != b,因为 b 在末尾包含 4 个未初始化的字节以构成 10 的长度。遗憾的是 std::strings 中可以包含 \0 字符,这在大多数情况下没有意义。
    【解决方案5】:

    没有内置的等效项。你必须推出自己的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));
    }
    

    【讨论】:

      【解决方案6】:

      字符串的子字符串构造函数可以做你想做的事,虽然它不完全等同于 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 会。

      【讨论】:

        【解决方案7】:

        使用类的构造函数:

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

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

        【讨论】:

        • OP 不是在寻找 strcpy,而是在寻找 strncpy
        • @pmr:OP 澄清说他只想要第一个 NULL 字节。
        • 不能防止读取超出缓冲区的已知范围。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-26
        • 1970-01-01
        • 1970-01-01
        • 2020-06-11
        • 1970-01-01
        相关资源
        最近更新 更多