strncpy截断问题

最近在对一块string对象进行拷贝到char数组的时候发现部分数据缺失了。
原因是strncpy在复制的时候,在遇到'\0'时,先复制过去,然后的把dest剩下置为了'\0'。所以,一旦源字符串中存在'\0'则会导致源数据被截断。为此我们可以采用memcpy进行拷贝操作(snprintf 拷贝的时候遇到'\0' 也会停止)

#include <iostream>
#include <cstdio>
int main()
{
    char src[] = "abcdefg\0hijklmn";
    char dest[100];
    memset(dest, 0, sizeof(dest));
    strncpy(dest, src, sizeof(dest));
    std::cout << dest << std::endl;
    return 0;
}


可以看到,发生了截断

一.各个拷贝函数的比较

1.strncpy

strncpy比strcpy稍微安全点,strncpy顶多拷贝n个字节,所以在dest不够的情况下,不会像strcpy出现拷贝越界。但也只是稍微安全点而已,如果src长度比n大,那么dest就不是以'\0'结尾的了,需要手动方式自行添加'\0'结束符(而用strcpy函数复制时目的串后会自动补'\0')。所以通常要这么使用:

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

给一串字符设置'\0'的方式有两种,分别为string[n]=0或string[n]='\0'。因为'\0'在ASCII码表中的编号就是十进制数0

#include <iostream>
#include <cstdio>
int main()
{
    char src[] = "abcdefg1234567890";
    char dest[5];
    memset(dest, 0, sizeof(dest));
    strncpy(dest, src, sizeof(dest));
    std::cout << dest << std::endl;
    dest[4] = 0;
    std::cout << dest << std::endl;
    return 0;
}


这段可以印证上面说到的两点:
●如果src长度比n大,那么dest就不是以'\0'结尾的了,所以abced后边是乱码
●'\0'在ASCII码表中的编号就是十进制数0
在高效方面strncpy也做得不好,它的大致实现这样的:

char *strncpy(char *dest, const char *src, size_t n)
{
    size_t i;
    for (i = 0; i < n && src[i] != '\0'; i++) {
        dest[i] = src[i];
    }
    for ( ; i < n; i++) {
        dest[i] = '\0';
    }
    return dest;
}

当src长度比n小时,未使用的字符,它会帮你填充为0,但我们往往会开一个大的Buffer来接收数据,如此必然有大量的多余空间,这样不仅速度慢,而且还会导致实存的消耗上涨。总之,strncpy的即不怎么安全,也不怎么高效。

2.memcpy

至于memcpy:void *memcpy(void *dest, const void *src, size_t n);
memcpy不安全,它只是做简单的拷贝而已,不过它很高效,如果你能保证不会越界的话,就使用memcpy吧。其实memcpy的实现就是一个一个赋值,strcpy也是一个一个赋值,不过,memcpy做了点优化,64位机器下,它会64位64位地拷贝,即8个字节8个字节地拷贝,对于除8的余数,再做单独处理,而strcpy是一个字节一个字节地拷贝,所以当有大量字符串需要拷贝时,理论上memcpy会比strcpy快8倍。

3.snprintf

int snprintf(char *str, size_t size, const char *format, cahr *source_str)
通过snprintf(dst_str, dst_size, “%s”, source_str); 可以将source_str拷贝到dst_str。snprintf的特点是安全,不管怎么着,它都能保证结果串str以\0结尾,哪怕dst_size不够大,它都能做好截断,同时在末尾添加上'\0'。

#include <iostream>
#include <cstdio>
int main()
{
    char src[] = "abcdefg1234567890";
    char dest[5];
    memset(dest, 0, sizeof(dest));
    snprintf(dest, sizeof(dest), "%s", src);
    std::cout << dest << std::endl;
    return 0;
}


因为末尾的数据被自动用\0 来作为结束符。
在性能方面,当source_str远长于dst_size时,该函数却很低效的,其他情况下该函数即安全又高效。
当source_str远长于dst_size时,该函数低效,是因为该函数的返回值为source_str的长度(不包括'\0'),那么它就必须将source_str全部过一遍,哪怕并不拷贝到dst_str中去。
注意,当source_str长度比dst_size小时,它不会把末尾的多余字符置零,所以它是很高效的,不会多做无用功。

二.测试对比

在Release条件下,g++编译器

#include <iostream>
#include <cstdio>
#include <chrono>

using namespace std::chrono;

int main()
{
    {
        char src[] = "abcdefg1234567890";
        char dest[2048];
        int len = 0;
        auto start = high_resolution_clock::now();
        for(int i = 0; i < 20000000; ++i){
            memset(dest, 0, sizeof(dest));
            len = strlen(src);
            len = sizeof(dest) - 1 > len? len: sizeof(dest) -1;
            memcpy(dest, src, len);
            dest[len] = '\0';
        }
        auto end = high_resolution_clock::now();
        std::cout <<"memcpy cost time: " << duration_cast<milliseconds>(end - start).count()<<" ms" << std::endl;
    }
    {
        char src[] = "abcdefg1234567890";
        char dest[2048];
        int len = 0;
        auto start = high_resolution_clock::now();
        for(int i = 0; i < 20000000; ++i) {
            memset(dest, 0, sizeof(dest));
            strncpy(dest, src, sizeof(dest));
        }
        auto end = high_resolution_clock::now();
        std::cout <<"strncpy cost time: " << duration_cast<milliseconds>(end - start).count()<<" ms" << std::endl;
    }
    {
        char src[] = "abcdefg1234567890";
        char dest[2048];
        int len = 0;
        auto start = high_resolution_clock::now();
        for(int i = 0; i < 20000000; ++i) {
            memset(dest, 0, sizeof(dest));
            snprintf(dest, sizeof(dest), "%s", src);
        }
        auto end = high_resolution_clock::now();
        std::cout <<"snprintf cost time: " << duration_cast<milliseconds>(end - start).count()<<" ms" << std::endl;
    }
    return 0;
}


可以看到在循环1千万次,memcpy的速度是最快的。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

草上爬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值