【问题标题】:How to copy a string into a char array in C++ without going over the buffer如何在不越过缓冲区的情况下将字符串复制到 C++ 中的 char 数组中
【发布时间】:2011-02-22 18:47:35
【问题描述】:

我想将一个字符串复制到一个 char 数组中,而不是超出缓冲区。

所以如果我有一个大小为 5 的 char 数组,那么我想从一个字符串中复制最多 5 个字节。

执行此操作的代码是什么?

【问题讨论】:

  • std::string,不知道其他类型的字符串。
  • 有字符串、一些或多或少知名的库字符串和不计其数的本土实现。

标签: c++ string g++ char


【解决方案1】:

更新:我想我会尝试将一些答案联系在一起,这些答案让我确信我自己最初的下意识 strncpy 反应很差。

首先,正如 AndreyT 在该问题的 cmets 中指出的那样,截断方法(snprintf、strlcpy 和 strncpy)通常不是一个好的解决方案。根据缓冲区长度检查字符串 string.size() 的大小并返回/抛出错误或调整缓冲区大小通常会更好。

如果在您的情况下截断是可以的,恕我直言,strlcpy 是最好的解决方案,它是确保空终止的最快/最少开销的方法。不幸的是,它不在许多/所有标准发行版中,因此不可移植。如果你做了很多这些,也许值得提供你自己的实现,AndreyT 给了一个example。它以 O(result length) 运行。参考规范还返回复制的字节数,这有助于检测源是否被截断。

其他好的解决方案是sprintfsnprintf。它们是标准的,因此是可移植的,并提供安全的空终止结果。它们比 strlcpy 有更多的开销(解析格式字符串说明符和变量参数列表),但除非你做了很多这些,否则你可能不会注意到差异。它也以 O(result length) 运行。 snprintf 总是安全的,如果格式说明符错误,sprintf 可能会溢出(正如其他人所指出的,格式字符串应该是"%.<N>s" 而不是"%<N>s")。这些方法还返回复制的字节数。

一个特殊的解决方案是strncpy。它以 O(buffer length) 运行,因为如果它到达 src 的末尾,它会将缓冲区的剩余部分归零。仅当您需要将缓冲区的尾部归零或确信目标和源字符串长度相同时才有用。另请注意,它并不安全,因为它不一定为空终止字符串。如果源被截断,则不会追加 null,因此使用 null 赋值依次调用以确保 null 终止:strncpy(buffer, str.c_str(), BUFFER_LAST); buffer[BUFFER_LAST] = '\0';

【讨论】:

  • @academicRobot:一般情况下,strncpy 更快或更慢取决于源字符串和目标缓冲区的相对大小。由于strncpy 在缓冲区较大的情况下会做大量浪费工作,因此在一般情况下滥用strncpy 不仅速度较慢,而且严重地变慢,速度要慢几个数量级。在这个例子中,唯一能节省时间的是不切实际的小目标缓冲区(只有 5 个字符)。
  • @academicRobot:您还坚持使用已知比缓冲区长的源字符串,从而歪曲了测试结果。单独测试这样的字符串是绝对没有意义的。
  • 最后,你的结论完全是假的。 sprintf/snprintf 确实不是最有效的函数,原因很明显。但这仅意味着人们必须更喜欢使用类似strlcpy 的功能,而不是几乎没用的strncpy。最后,在启用截断的字符串复制上下文中性能的重要性是另一个问题。被视为可接受的字符串截断通常表示用户界面应用程序。谁需要用户界面的性能?
  • @AndreyT 谢谢先生,我可以再给我一个! :) 你在每一点上都是对的,除了我不会承认最后一点(只是为了用户界面,真的吗?!?!)。但是使用类似 strlcpy 的函数可能是最好的选择。
  • 好吧,想想看:你复制了一个字符串并且你同意丢失该字符串的一部分(如果它太长的话)。 IE。您复制的数据会失真/损坏/损坏/截断(根据您的喜好选择)。在什么情况下可以接受?我能想到的唯一上下文是用户界面:你告诉用户一些很长的事情,如果你把它剪掉一点(比如一个错误列表,例如,当只是第一个足够的)。当截断的数据正常时,你能想出另一个上下文吗?
【解决方案2】:
std::string my_string("something");
char* my_char_array = new char[5];
strncpy(my_char_array, my_string.c_str(), 4);
my_char_array[4] = '\0'; // my_char_array contains "some"

使用strncpy,您最多可以将n 个字符从源复制到目标。但是,请注意,如果源字符串的长度最多为 n 个字符,则目标字符串不会以 null 结尾;您必须自己将终止的空字符放入其中。

长度为 5 的 char 数组最多可以包含 4 个字符的字符串,因为第 5 个必须是终止空字符。因此在上面的代码中,n = 4.

【讨论】:

    【解决方案3】:

    一些不错的 libc 版本提供了非标准但很好的替代 strcpy(3)/strncpy(3) - strlcpy(3)

    如果您没有,源代码可从here 库中免费获得OpenBSD

    【讨论】:

      【解决方案4】:
      std::string str = "Your string";
      char buffer[5];
      strncpy(buffer, str.c_str(), sizeof(buffer)); 
      buffer[sizeof(buffer)-1] = '\0';
      

      最后一行是必需的,因为 strncpy 不能保证 NUL 终止字符串(昨天已经讨论过动机)。

      如果你使用宽字符串,而不是sizeof(buffer),你会使用sizeof(buffer)/sizeof(*buffer),或者更好的是,像宏

      #define ARRSIZE(arr)    (sizeof(arr)/sizeof(*(arr)))
      /* ... */
      buffer[ARRSIZE(buffer)-1]='\0';
      

      【讨论】:

        【解决方案5】:

        首先,strncpy 几乎肯定不是你想要的。 strncpy 是为相当特定的目的而设计的。它在标准库中几乎完全是因为它已经存在,而不是因为它通常很有用。

        做你想做的最简单的方法可能是:

        sprintf(buffer, "%.4s", your_string.c_str());
        

        strncpy 不同,这保证了结果将被 NUL 终止,但如果源比指定的短,则不会在目标中填充额外数据(尽管当目标长度为5).

        【讨论】:

        • +1 以获得唯一答案。但是,相对于strncpy(buffer, str.c_str(), 4); buffer[4] = '\0';,这不是很多不必要的开销吗?
        • @academicRobot:你有没有测试过,或者注意到了不同之处? :) 我更喜欢sprintf 解决方案,它更简单一些。只有当性能不足时,我才会分析,也许会发现这是一个问题,用strncpy 测试它,也许会发现它工作得更好。
        • 首选安全版本snprintf,它允许您指定目标缓冲区大小。
        • @academicRobot:一般情况下,strncpy 更快或更慢取决于源字符串和目标缓冲区的相对大小。由于strncpy 在缓冲区较大的情况下会浪费大量的工作,因此在一般情况下滥用strncpy 不仅更慢,而且极其缓慢,慢了几个数量级.在这个例子中,唯一能节省时间的是不切实际的小目标缓冲区(只有 5 个字符)。
        • @NicolBolas:这早于 C++11,所以 snprintf 当时不是 C++ 的一部分。即便如此,在此处指定精度会限制可以写入缓冲区的数据量,防止缓冲区溢出(没有指定大于缓冲区的大小 - 如果您这样做,snprintf 不会防止缓冲区溢出。
        【解决方案6】:

        使用函数 strlcpy损坏的链接,并且在目标站点上找不到材料,如果您的实现提供了一个(该函数不在标准 C 库中),但它被广泛接受为一个事实上的标准名称,用于零终止字符串的“安全”有限长度复制函数。

        如果您的实现不提供strlcpy 功能,请自行实现。例如,这样的事情可能对你有用

        char *my_strlcpy(char *dst, const char *src, size_t n)
        {
          assert(dst != NULL && src != NULL);
        
          if (n > 0)
          {
            char *pd;
            const char *ps;
        
            for (--n, pd = dst, ps = src; n > 0 && *ps != '\0'; --n, ++pd, ++ps)
              *pd = *ps;
        
            *pd = '\0';
          }
        
          return dst;
        }
        

        (实际上,事实上接受的strlcpy 返回size_t,因此您可能更愿意实现接受的规范而不是我上面所做的)。

        请注意建议为此使用strncpy 的答案。 strncpy 不是安全的有限长度字符串复制功能,不应该用于该目的。虽然您可以强制 strncpy 为此目的“工作”,但它仍然类似于用锤子驱动木螺丝。

        【讨论】:

        • 这个限制是在某些情况下你应该检测到缓冲区溢出。返回副本大小而不是已知的 dst 会更有趣。在这种情况下,当 copy_size > n 或知道有多少缓冲区已用完时,可以处理错误。
        【解决方案7】:

        如果你总是有一个大小为 5 的缓冲区,那么你可以这样做:

        std::string s = "Your string";
        char buffer[5]={s[0],s[1],s[2],s[3],'\0'};
        

        编辑: 当然,假设你的 std::string 足够大。

        【讨论】:

        • 这根本不能扩展到任意大小的缓冲区。
        • 对。这就是为什么我写了“总是有一个大小为 5 的缓冲区”。
        【解决方案8】:

        这正是std::string 的复制功能所做的。

        #include <string>
        #include <iostream>
        
        int main()
        {
        
            char test[5];
            std::string str( "Hello, world" );
        
            str.copy(test, 5);
        
            std::cout.write(test, 5);
            std::cout.put('\n');
        
            return 0;
        }
        

        如果你需要空终止,你应该这样做:

        str.copy(test, 4);
        test[4] = '\0';
        

        【讨论】:

        • 不错的解决方案,但现在不再没有警告:warning C4996: 'std::basic_string&lt;_Elem,_Traits,_Alloc&gt;::copy': Function call with parameters that may be unsafe - this call relies on the caller to check that the passed values are correct. To disable this warning, use -D_SCL_SECURE_NO_WARNINGS. See documentation on how to use Visual C++ 'Checked Iterators'
        • @PaulWilliams 在答案的最后一部分中说明。
        • null 终止应该在复制结束之后,而不是在复制缓冲区的末尾。如果目标缓冲区已经有内容,那么输出字符串中就会有垃圾。
        • 终止聊天缓冲区的通用方法是``` const size_t LEN = 5;字符测试[LEN]; size_t len = str.copy(test, LEN-1);测试[len] = '\0'; ```
        【解决方案9】:
        char mystring[101]; // a 100 character string plus terminator
        char *any_input;
        any_input = "Example";
        iterate = 0;
        while ( any_input[iterate] != '\0' && iterate < 100) {
            mystring[iterate] = any_input[iterate];
            iterate++;
        }
        mystring[iterate] = '\0';
        

        这是基本的高效设计。

        【讨论】:

          【解决方案10】:

          我认为 snprintf() 是非常安全和最简单的

          snprintf ( buffer, 100, "The half of %d is %d", 60, 60/2 );
          

          空字符会自动结束:)

          【讨论】:

            【解决方案11】:
            void stringChange(string var){
            
                char strArray[100];
                strcpy(strArray, var.c_str()); 
            
            }
            

            我想这应该可行。它会将表单字符串复制到 char 数组中。

            【讨论】:

              【解决方案12】:

              最流行的答案很好,但空终止不是通用的。以空值终止字符缓冲区的通用方法是:

              std::string aString = "foo";
              const size_t BUF_LEN = 5;
              char buf[BUF_LEN];
              size_t len = aString.copy(buf, BUF_LEN-1); // leave one char for the null-termination
              buf[len] = '\0';
              

              len 是复制的字符数,因此它在0BUF_LEN-1 之间。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2012-07-17
                • 1970-01-01
                • 2012-08-15
                • 2015-10-29
                • 2021-12-31
                • 1970-01-01
                • 2012-04-24
                • 1970-01-01
                相关资源
                最近更新 更多