【问题标题】:Copying n characters using memcpy使用 memcpy 复制 n 个字符
【发布时间】:2016-08-07 06:31:15
【问题描述】:

我正在尝试使用 memcpy 复制确切数量的 32 个字符,但是我在正确使用它时遇到问题,因为多个在线 g++ 编译器以及我机器上的编译器使用相同的源给出的结果略有不同代码。

代码:

#include <iostream>
#include <cstring>

int main()
{
    const char* source = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eu ipsum nec elit mattis consequat. Curabitur sollicitudin ligula et quam bibendum euismod.";
    char dest[32];

    std::memcpy(&dest, source, sizeof(dest));
    std::cout << dest << "(" << strlen(dest) << ")";
}

here (G++4.9.2) 编译的代码。

输出不包含 32 个字符(正在添加垃圾值):

Lorem ipsum dolor sit amet, cons †¿(36)

here (G++4.9) 编译的代码。

输出包含所需的结果:

Lorem ipsum dolor sit amet, cons(32)

我机器上的输出类似于第一个输出(36 个字符)。

为什么每个结果都不同?

memcpy 复制 n 个字符的有效用法应该是什么?

【问题讨论】:

  • 你对memcpy的使用是正确的,但是判断其结果的方法是错误的。

标签: c++ c++11 memcpy


【解决方案1】:

strlen 函数和operator&lt;&lt; (const char *) 函数仅适用于 C 风格的字符串。它们不能用于输出或测量任意数据块的长度。

想一想——他们怎么可能确定长度?他们可以使用什么方法?

为什么每个结果都不一样?

因为您使用的函数只能用于非 C 风格字符串的 C 风格字符串。这是一个错误,其行为会因平台内存布局的具体情况而有所不同。

memcpy 复制 n 个字符的有效用法应该是什么?

就是这样。你复制了字符。但是现在你只有一堆字符,而不是字符串。如果你使用打印成串字符的函数,它们就可以正常工作。

试试这个:

#include <iostream>
#include <cstring>

int main()
{
    const char* source = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eu ipsum nec elit mat
    char dest[32];

    std::memcpy(&dest, source, sizeof(dest));

    for (int i = 0; i < sizeof(dest); ++i)
        std::cout << dest[i];
}

【讨论】:

  • 所以基本上,如果我想打印 dest 变量,我将不得不对字符串进行空终止或使用 stl 方法(参见 @Luke 答案)?
  • @Lucas 对。您可以使用打印 C 样式字符串的代码,但必须将其设为 C 样式字符串。否则,请使用打印一堆字符的代码。
【解决方案2】:

dest 不是以空值结尾的。因此,像strlenoperator &lt;&lt; 这样的函数并不知道它们已经到达缓冲区的末尾,并且即使在达到 32 个字符之后也会继续继续。它们会在dest[31] 之后的未知内存中遇到 null 时停止,这可能是在 10、1000、1000000 字节之后甚至根本没有。你需要的是:

#include <iostream>
#include <cstring>

int main()
{
    const char* source = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eu ipsum nec elit mattis consequat. Curabitur sollicitudin ligula et quam bibendum euismod.";
    char dest[33];

    std::memcpy(&dest, source, sizeof(dest)-1);
    dest[32] = '\0';
    std::cout << dest << "(" << strlen(dest) << ")";
}

【讨论】:

  • 这很聪明,但使用strlcpy 会更容易吗?它将在第一个空值时停止复制?不知道我是否正确,但似乎很简单:char dest[32 + 1]; strlcpy(&amp;dest, source, sizeof(dest));
  • 是的,这也是一个解决方案。
【解决方案3】:

memcpy 函数不检查源中的任何终止空字符 - 它总是精确复制 num 个字节。你应该以 null 结束。

【讨论】:

    【解决方案4】:

    你可以使用std::string:

    #include <iostream>
    #include <string>
    
    int main()
    {
        const char* source = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eu ipsum nec elit mattis consequat. Curabitur sollicitudin ligula et quam bibendum euismod.";
    
        std::string s(source, 32);
        std::cout << s << "(" << s.length() << ")";
    }
    

    对于memcpy,你通常不想在c风格的字符串上使用它,因为字符串的长度是内存块的大小减1。

    【讨论】:

    • 我喜欢这个解决方案。它也可以用来发送缓冲数据包吗?我的意思是:const char* n = "Something"; network-&gt;Packet(std::string(n, 32).c_str() /* const void* data */, 32 /* size */);
    【解决方案5】:

    这是因为C++中内存分配的奇怪方法。

    其中一种解决方案是确定数组的长度。 如果在函数内部定义了一个数组,则它不会显示为充满了 nul。更明显的是,strlen() 函数通过查找第一个 nul 字节来计算字符串的长度。在函数内部,变量最初并未初始化,而是包含任意数据。这块内存,直接取自操作系统堆。

    如果你把数组这样放在外面:

    #include <iostream>
    #include <cstring>
    using namespace std;
    char dest[32];
    
    int main(int argc, char** argv) {
    ....
    

    它将正常运行,因为在函数外部声明的任何变量最初都初始化为零。

    解决此问题的另一种方法是,就像@Lucas 所说,在外面留一个字节为空,即:

    char dest[33];
    memcpy(dest, source, sizeof(char) * 32);
    

    这不受数组是否位于函数之外的影响。

    具体来说,strlen函数原理与此类似:

    int strlen(char* str)
    {
        for (int i = 0; ; i++)
            if (str[i] == 0)
                return i;
        return 0;
    }
    

    有些用户指出我不能保证第 33 个字节是空的。现在我找到了解决方案:

    char dest[33];
    memset(dest, 0, sizeof(char) * 33);
    memcpy(dest, source, sizeof(char) * 32);
    

    或者简单地将最后一个字节设置为 nul。

    char dest[33];
    dest[32] = 0;
    

    一些更安全、更美观的方法包括直接内存分配。然而根据一些统计数据,new 命令和 ma​​lloc() 函数会导致性能下降。

    char *dest = new char[32];
    memcpy(dest, source, sizeof(char) * 32);
    

    如果你使用下面的代码,你会遇到意想不到的结果。

    char *dest = new char[32];
    memcpy(dest, source, sizeof(char) * 32);
    

    因此,在使用 C/C++ 编程时,请务必考虑边界。

    【讨论】:

    • 您无法保证第 33 个字节(索引 32)将为空。它将包含声明 dest 时的任何值。
    • 你把NULL(空指针常量)和nul(用于终止C风格字符串的零字符值)混淆了
    • 前2段看不懂。 “其中一个解决方案是确定数组的长度”——OP 已经知道了。 “如果在函数内部定义了一个数组,它就不会像充满了 nuls 一样显示出来”——什么?
    • @RadLexus 在函数内部定义时不会被初始化。尝试自己编译运行。
    猜你喜欢
    • 2022-07-29
    • 1970-01-01
    • 2020-02-24
    • 1970-01-01
    • 2013-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多