【问题标题】:Problems with strncpy and how to fix itstrncpy 的问题以及如何解决它
【发布时间】:2014-01-18 21:45:47
【问题描述】:

我正在学习 C 并阅读 Learn C The Hard Way (ISBN-10: 0-321-88492-2)。我被困在练习 17“如何打破它”上。

这是书中的问题:

由于strncpy不好,这个程序有一个bug 设计的。去阅读有关 strncpy 的内容,然后尝试找出什么时候发生 您提供的名称或地址大于 512 个字节。通过以下方式解决此问题 只需将最后一个字符强制为 '\0' 以便它始终设置为 no 不管是什么(这是 strncpy 应该做的)。

我已经阅读了一些关于 strncpy 的内容,我知道它是不安全的,因为它不会在字符串的末尾添加空字节。但是,我不知道如何将大量字节传递给函数,也不确定如何解决空字节问题。

下面是使用strncpy的函数,MAX_DATA设置为512。

void Database_set(struct Connection *conn, int id, const char *name, const char *email)
{
    struct Address *addr = &conn->db->rows[id];
    if(addr->set) die("Already set, delete it first");

    addr->set = 1;
    // WARNING: bug, read the "How To Break It" and fix this
    char *res = strncpy(addr->name, name, MAX_DATA);
    // demonstrate the strncpy bug
    if(!res) die("Name copy failed");

    res = strncpy(addr->email, email, MAX_DATA);
    if(!res) die("Email copy failed");
}

如何破解它 - 编辑

下面是一个如何破解strncpy的例子:

void Database_set(struct Connection *conn, int id, const char *name, const char *email)
{
    struct Address *addr = &conn->db->rows[id];
    if(addr->set) die("Already set, delete it first");

    addr->set = 1;
    // WARNING: bug, read the "How To Break It" and fix this

    char name2[] = {
      'a', 's', 't',
      'r', 'i', 'n', 'g'
    };
    char *res = strncpy(addr->name, name2, MAX_DATA);
    // demonstrate the strncpy bug
    if(!res) die("Name copy failed");

    res = strncpy(addr->email, email, MAX_DATA);
    if(!res) die("Email copy failed");
}

要解决此问题,请在字符串末尾添加一个空字节。将name2 更改为:

  char name2[] = {
    'a', 's', 't',
    'r', 'i', 'n', 'g', '\0'
  };

或者,在 strncpy 函数调用上方添加以下行

names2[sizeof(names2)-1] = '\0';

【问题讨论】:

  • 这太笼统了。显示一些代码。
  • 传入大量字节与传入少量字节相同,只是更大:)
  • 我写过关于strncpy()here的文章。
  • 我会要求退款。在函数Database_setchar *res = strncpy(...); // demonstrate the strncpy bug if(!res) die("Name copy failed");strncpy 从不返回 NULL,它总是返回目标指针。
  • 我已经包含了我正在处理的函数。 @NigelHarper 所以我可以传入一个非常长的字符串,它会大于 512 字节吗?对不起,我非常靠近 C!

标签: c


【解决方案1】:

修复strncpy 错误的另一种方法是修复printf 调用

void Address_print(struct Address *addr)
{
    printf("%d %.*s %.*s\n",
            addr->id, sizeof(addr->name), addr->name, sizeof(addr->email), addr->email);
}

这限制 printf 最多输出整个字符数组,但不能更多。

【讨论】:

    【解决方案2】:

    为什么不直接用 strlcpy 替换 strncpy? 根据 strlcpy 手册页:

     EXAMPLES
     The following sets chararray to ``abc\0\0\0'':
    
           (void)strncpy(chararray, "abc", 6);
    
     The following sets chararray to ``abcdef'' and does not NUL terminate
     chararray because the length of the source string is greater than or
     equal to the length parameter.  strncpy() only NUL terminates the
     destination string when the length of the source string is less than the
     length parameter.
    
           (void)strncpy(chararray, "abcdefgh", 6);
    
     Note that strlcpy(3) is a better choice for this kind of operation.  The
     equivalent using strlcpy(3) is simply:
    
           (void)strlcpy(buf, input, sizeof(buf));
    
    
     The following copies as many characters from input to buf as will fit and
     NUL terminates the result.  Because strncpy() does not guarantee to NUL
     terminate the string itself, it must be done by hand.
    
           char buf[BUFSIZ];
    
           (void)strncpy(buf, input, sizeof(buf) - 1);
           buf[sizeof(buf) - 1] = '\0';
    

    【讨论】:

      【解决方案3】:

      strncpy 的手册页实际上给出了如何修复此错误的示例代码:

      strncpy(buf, str, n);
      if (n > 0)
         buf[n - 1]= '\0';
      

      【讨论】:

        【解决方案4】:

        假设str1str2 是字符数组,您可以执行以下操作:

        strncpy(str1, str2, sizeof(str1) - 1);
        str1[sizeof(str1)-1] = '\0';
        

        这将始终将最后一个字符设置为\0,无论str2 有多长。但请记住,如果str2 大于str1,则字符串将被截断。

        【讨论】:

        • @NigelHarper 如果您使用strlen(str1),则永远无法存储比str1 中当前更大的字符串。 sizeof(str1) - 1 在这种情况下是正确的方法。
        • @NigelHarper 不,他的意思是sizeof,假设目标是一个数组(这很常见)。 strlen() 没有任何意义,特别是如果目标在开始时未初始化。除此之外,我看不出这个答案有什么问题,它真的不值得投反对票。
        • 如果 str1 不是数组而是指针,这将完全被破坏。如果 str1 是指向比 char 指针少的字节的指针,则情况更糟。在尝试处理 UTF-8 数据时,它非常糟糕,现在绝大多数字符串都将在其中创建无效字符串。
        • @gasher729; This is totally broken if str1 is not an array but a pointer.:我认为你应该再读一遍答案的第一行:假设 str1 和 str2 是字符数组
        【解决方案5】:

        这里的主要问题是addr->name没有初始化,所以它是一个空指针,它没有指向哪里。

        所以在你可以先使用strncpy之前你必须给addr->name分配内存,否则它不会起作用。

        而且由于它是一个NULL指针,如果你没有设置它就会返回NULL,那么后面的if语句就会为真,并且die函数会停止程序。

        可以看源码,Database_create函数没有从struct Address初始化两个字符串指针。

        【讨论】:

          猜你喜欢
          • 2011-10-08
          • 2011-07-23
          • 2018-02-08
          • 1970-01-01
          • 2023-01-29
          • 2023-04-07
          • 2021-08-11
          • 1970-01-01
          • 2012-05-20
          相关资源
          最近更新 更多