【问题标题】:Copying n chars with strncpy more efficiently in C在 C 中更有效地使用 strncpy 复制 n 个字符
【发布时间】:2012-05-12 14:23:27
【问题描述】:

考虑到max 的字符数,我想知道是否有一种更清洁、更有效的方法来执行以下strncpy。我觉得我做得太过分了。

int main(void)
{

        char *string = "hello world foo!";
        int max = 5;

        char *str = malloc (max + 1);
        if (str == NULL)
                return 1;
        if (string) {
                int len = strlen (string);
                if (len > max) {
                        strncpy (str, string, max);
                        str[max] = '\0';
                } else {
                        strncpy (str, string, len);
                        str[len] = '\0';
                }
                printf("%s\n", str);
        }
        return 0;
}

【问题讨论】:

  • 谢谢大家,所有的答案都很好!

标签: c string malloc strncpy


【解决方案1】:

基本上,您正在重新发明 1996 年推出的 strlcpy - 请参阅 Todd C. Miller 和 Theo de Raadt 的 strlcpy and strlcat - consistent, safe, string copy and concatenation 论文。你可能没有听说过它,因为它是 refused to be added to glibc,被 glibc 维护者称为“非常低效的 BSD 垃圾”,即使被所有其他操作系统采用,它也一直战斗到今天 - 请参阅 Damien Miller 的 Secure Portability 论文(部分4:选择正确的 API)。

您可以使用libbsd 项目(打包在 Debian、Ubuntu 和其他发行版上)或通过简单地复制在网络上很容易找到的源代码(例如在此答案中的两个链接上)在 Linux 上使用 strlcpy。

但是回到你的问题,在你的情况下什么是最有效的,在这里你没有使用源字符串长度是我的想法,基于来自 OpenBSD 的 strlcpyhttp://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libc/string/strlcpy.c?rev=1.11 但没有检查原始字符串的长度,它可能很长,但仍以正确的 '\0' 结尾:

char *d = str;            // the destination in your example
const char *s = string;   // the source in your example
size_t n = max;           // the max length in your example

/* Copy as many bytes as will fit */
if (n != 0) {
    while (--n != 0) {
        if ((*d++ = *s++) == '\0')
            break;
    }
}

/* Not enough room in dst, add NUL */
if (n == 0) {
    if (max != 0)
        *d = '\0';      /* NUL-terminate dst */
}

这是http://cantrip.org/strlcpy.c 上使用 memcpy 的 strlcpy 版本:

/*
 * ANSI C version of strlcpy
 * Based on the NetBSD strlcpy man page.
 *
 * Nathan Myers <ncm-nospam@cantrip.org>, 2003/06/03
 * Placed in the public domain.
 */

#include <stdlib.h>  /* for size_t */

size_t
strlcpy(char *dst, const char *src, size_t size)
{
    const size_t len = strlen(src);
    if (size != 0) {
        memcpy(dst, src, (len > size - 1) ? size - 1 : len);
        dst[size - 1] = 0;
    }
    return len;
}

我认为哪个更有效取决于源字符串。对于非常长的源字符串,strlen 可能需要很长时间,如果您不需要知道原始长度,那么第一个示例对您来说可能会更快。

这完全取决于您的数据,因此对真实数据进行分析是找出答案的唯一方法。

【讨论】:

    【解决方案2】:

    我根本不会使用strncpy。至少如果我理解你想要做什么,我可能会做这样的事情:

    char *duplicate(char *input, size_t max_len) {
        // compute the size of the result -- the lesser of the specified maximum
        // and the length of the input string. 
        size_t len = min(max_len, strlen(input));
    
        // allocate space for the result (including NUL terminator).
        char *buffer = malloc(len+1);
    
        if (buffer) {
            // if the allocation succeeded, copy the specified number of 
            // characters to the destination.
            memcpy(buffer, input, len);
            // and NUL terminate the result.
            buffer[len] = '\0';
        }
        // if we copied the string, return it; otherwise, return the null pointer 
        // to indicate failure.
        return buffer;
    }
    

    【讨论】:

    • @IgnacioVazquez-Abrams:实际上,重读并考虑到他对速度的强调(通常是 sprintf 的弱点),我将其改为使用 memcpy。但是,是的,我打算在那里放一个....
    • 你的版本漂亮优雅。我会 +1,但我认为你没有解释为什么你每个步骤都做得很好
    • 我要补充的最后一件事是 if(buffer) 是“光滑的”,因为如果内存分配失败,它将通过一个空指针。无论如何,为你+1。我的版本需要几个步骤并将它们分开并且不那么光滑。真该死那些在深夜能思考的人
    【解决方案3】:

    首先,对于 strncpy,“没有空字符隐式附加到目标的末尾,因此只有当源中 C 字符串的长度小于 num 时,目标才会以空字符结尾。”

    我们使用 memcpy() 是因为 strncpy() 在每个副本上检查每个字节是否为 0。我们已经知道字符串的长度,memcpy() 做得更快。

    先计算字符串的长度,再决定分配和复制什么

    int max = 5;               // No more than 5 characters
    
    int len = strlen(string);  // Get length of string
    int to_allocate = (len > max ? max : len); // If len > max, it'll return max. If len <= max, it'll return len. So the variable will be bounded within 0...max, whichever is smaller
    
    char *str = malloc(to_allocate + 1); // Only allocate as much as we need to
    if (!str) { // handle bad allocation here }
    
    memcpy(str,string,to_allocate); // We don't need any if's, just do the copy. memcpy is faster, since we already have done strlen() we don't need strncpy's overhead
    
    str[to_allocate] = 0; // Make sure there's a null terminator
    

    【讨论】:

    • 我们不应该将malloc返回的内存归零吗?
    • @Asha 您可以通过调用 calloc() 将其归零
    • @Asha 说,没有理由将其归零
    【解决方案4】:

    我相信这就足够了:

    char *str = malloc(max+1);
    if(! str)
    return 1;
    
    int len = strlen(string);  
    memset(str, 0, max+1);
    int copy = len > max ? max : len;
    strncpy(str, string, copy);
    

    【讨论】:

    • 如果您想将分配的内存归零,请使用 calloc()。但是,此解决方案效率低下。此外,memcpy 更快,因为我们已经完成了 strlen() 并且不需要检查每个字节的 NULL。
    【解决方案5】:

    strncpy() 在遇到 NUL 时会自动停止;通过max 不检查就足够了。

    【讨论】:

    • @std''OrgnlDave:你确实看到下一行添加了一个NUL,对吧?
    【解决方案6】:

    您可以通过以下方式减少代码量:

    int main(void)
    {
        char *string = "hello world foo!";
        int max = 5;
    
        char *str = malloc(max + 1);
        if (str == NULL)
            return 1;
        if (string) {
            int len = strlen(string);
            if (len > max)
                len = max;
            strncpy(str, string, len);
            str[len] = '\0';
            printf("%s\n", str);
        }
        return 0;
    }
    

    您无法进一步加快strncpy() 的速度。您可以通过以下方式减少时间:

    char string[] = "hello world foo!";
    

    然后改用sizeof(string) 来避免strlen()

    请注意,如果最大大小很大而要复制的字符串很小,那么strncpy() 会在目标字符串中每个未使用的位置上写入一个空值,这确实会减慢速度。

    【讨论】:

    • 如果你想“加快速度”,可以使用 memcpy() 而不是 strncpy;因为我们已经完成了 strlen() 我们不需要 strncpy 在每个字节副本中检查 NULL 的开销
    猜你喜欢
    • 1970-01-01
    • 2015-10-16
    • 2010-11-13
    • 2016-08-07
    • 2018-07-08
    • 2012-10-05
    • 1970-01-01
    • 2020-01-09
    相关资源
    最近更新 更多