#include <string.h>

char *strcpy(char *dest, const char *src);

char *strncpy(char *dest, const char *src, size_t n);

 

(1)最多复制n个字节

(2)如果前面n个里头没有null bytes,那么dest字符串就不是以null结尾。(那这还算一个字符串么???,又或者说,dest本身就是一个字符串,人家分配内存的时候,本身就有一个\0结尾,应该是这样。)

#include<stdio.h>
#include<stdlib.h>
void main(){

    char dst[] = "hello world";
    char *src="shit!";
     int num=5;
    strncpy(dst, src, num);
    

    puts(dst);
}

num为5的时候,复制了src前五个字符到dst,末尾的\0没有复制,所以输出shit! world

num为6的时候,复制src的前六个字符到dst,唯美的\0作为第六个字符被复制,所以输出shit!

(3)如果src的长度小于n,那么strncpy会继续填充null bytes 以达到n个到dest中。

例如, 当num为7,也就是说第六个\0后,还要复制一个的时候,第七个位置就默认填充\0:

#include<stdio.h>
#include<stdlib.h>
void main(){

    char dst[] = "hello world";
    char *src="shit!";
    int num=7;

    strncpy(dst, src,num);


    puts(dst);
    int len=11;
    int i=-1;
    while(i++<len){
        printf("dst[%d]=[%c]\n",i,dst[i]);
    }
}

 

输出

root@Ubuntu32:/home/zhangbin/STM/platinum/DMS/minidlna/linux/test# ./testStrncpy
shit!
dst[0]=[s]
dst[1]=[h]
dst[2]=[i]
dst[3]=[t]
dst[4]=[!]
dst[5]=[]  复制的\0
dst[6]=[]  复制的\0
dst[7]=[o]
dst[8]=[r]
dst[9]=[l]
dst[10]=[d]
dst[11]=[]
root@Ubuntu32:/home/zhangbin/STM/platinum/DMS/minidlna/linux/test# 

==================================================

注意,以上主要讨论dest内存空间充足的情况,至于dest不充足的情况,程序员一般不会这么干,我试了下,居然不会段错误,也可以执行下去:

比如,dst加上\0总共有6个位置,如果dst是char * dst,肯定段错误,但是用数组就不会!!!!

#include<stdio.h>
#include<stdlib.h>
void main(){

    char src[] = "hello world";
    char dst[]="shit!";
    int num=9;//复制9个到dst

    strncpy(dst, src,num);


    puts(dst);
    int len=7;
    int i=-1;  //注意要从-1开始,下面的i++会变成0开始。
    while(i++<len){
        printf("dst[%d]=[%c]\n",i,dst[i]);
    }
}

root@Ubuntu32:/home/zhangbin/STM/platinum/DMS/minidlna/linux/test# gcc -o testStrncpy1 testStrncpy1.c
testStrncpy1.c: In function ‘main’:
testStrncpy1.c:9:5: warning: incompatible implicit declaration of built-in function ‘strncpy’ [enabled by default]
strncpy(dst, src,num);
^
root@Ubuntu32:/home/zhangbin/STM/platinum/DMS/minidlna/linux/test# ./testStrncpy1
hello worlo world   puts的输出显示, 居然坚定不移的复制了9个到dst,dst没空间了啊,内存地址还连续的输出了?
dst[0]=[h]
dst[1]=[e]
dst[2]=[l]
dst[3]=[l]
dst[4]=[o]
dst[5]=[ ]  第六个,src第六个是空格
dst[6]=[w] 继续从src复制第七个w
dst[7]=[o]
root@Ubuntu32:/home/zhangbin/STM/platinum/DMS/minidlna/linux/test#

==========================================

STRCPY(3) Linux Programmer’s Manual STRCPY(3)

NAME
strcpy, strncpy – copy a string

SYNOPSIS
#include <string.h>

char *strcpy(char *dest, const char *src);

char *strncpy(char *dest, const char *src, size_t n);

DESCRIPTION
The strcpy() function copies the string pointed to by src, including
the terminating null byte (‘\0’), to the buffer pointed to by dest.
The strings may not overlap, and the destination string dest must be
large enough to receive the copy. Beware of buffer overruns! (See
BUGS.)

注意,不会覆盖,所以dest必须有足够的空间存储。

The strncpy() function is similar, except that at most n bytes of src
are copied. Warning: If there is no null byte among the first n bytes
of src, the string placed in dest will not be null-terminated.

 

If the length of src is less than n, strncpy() writes additional null
bytes to dest to ensure that a total of n bytes are written.

A simple implementation of strncpy() might be:

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]; //一直复制,但不复制src的\0,哪怕未达到n
for ( ; i < n; i++) //如果此时i还未达到n(说明还没复制0--n-1共n个,因为src的数据不足),那么dest的第n个位置(从0算起)起,要逐个赋值为\0
  dest[i] = '\0';

return dest;
}

 

RETURN VALUE
The strcpy() and strncpy() functions return a pointer to the destina‐
tion string dest.

CONFORMING TO
SVr4, 4.3BSD, C89, C99.

NOTES
Some programmers consider strncpy() to be inefficient and error prone.
If the programmer knows (i.e., includes code to test!) that the size
of dest is greater than the length of src, then strcpy() can be used.

One valid (and intended) use of strncpy() is to copy a C string to a
fixed-length buffer while ensuring both that the buffer is not over‐
flowed and that unused bytes in the target buffer are zeroed out (per‐
haps to prevent information leaks if the buffer is to be written to
media or transmitted to another process via an interprocess communica‐
tion technique).

If there is no terminating null byte in the first n bytes of src,
strncpy() produces an unterminated string in dest. You can force ter‐
mination using something like the following:

strncpy(buf, str, n);
if (n > 0)
buf[n – 1]= ‘\0’;

(Of course, the above technique ignores the fact that information con‐
tained in src is lost in the copying to dest.)

Some systems (the BSDs, Solaris, and others) provide the following
function:

size_t strlcpy(char *dest, const char *src, size_t size);

This function is similar to strncpy(), but it copies at most size-1
bytes to dest, always adds a terminating null byte, and does not pad
the target with (further) null bytes. This function fixes some of the
problems of strcpy() and strncpy(), but the caller must still handle
the possibility of data loss if size is too small. The return value of
the function is the length of src, which allows truncation to be easily
detected: if the return value is greater than or equal to size, trunca‐
tion occurred. If loss of data matters, the caller must either check
the arguments before the call, or test the function return value.
strlcpy() is not present in glibc and is not standardized by POSIX, but
is available on Linux via the libbsd library.

BUGS
If the destination string of a strcpy() is not large enough, then any‐
thing might happen. Overflowing fixed-length string buffers is a
favorite cracker technique for taking complete control of the machine.
Any time a program reads or copies data into a buffer, the program
first needs to check that there’s enough space. This may be unneces‐
sary if you can show that overflow is impossible, but be careful: pro‐
grams can get changed over time, in ways that may make the impossible
possible.

SEE ALSO
bcopy(3), memccpy(3), memcpy(3), memmove(3), stpcpy(3), stpncpy(3),
strdup(3), string(3), wcscpy(3), wcsncpy(3)

COLOPHON
This page is part of release 3.54 of the Linux man-pages project. A
description of the project, and information about reporting bugs, can
be found at http://www.kernel.org/doc/man-pages/.

GNU 2012-07-19 STRCPY(3)

=========================对于strncpy的在man里所描述的表现,与我同样有疑惑的这位小哥说:

停止使用 strncpy 函数! 并且给出了解决办法呢:

也许你曾经被多次告知,要使用 strncpy 替代 strcpy 函数,因为 strncpy 函数更安全。而今天我要告诉你,strncpy 是不安全的,并且是低效的,strncpy 的存在是由于一个历史原因造成的,你不应当再使用 strncpy 函数。

下面我来解释为什么 strncpy 函数是不安全并且是低效的,以及我们应该使用那些替代函数。

我以前对 strncpy 一直存在误解,直到有一次出现了 BUG。好不容易定位到 strncpy 身上,然后仔细查看文档,才明白问题所在。

误解一:如果 src 长度小于 n, 那么strncpy 和 strcpy 效果一样?

错,事实上,strncpy 还会把 dest 剩下的部分全部置为 0!

一直认为 strncpy 只是比 strcpy 多了长度校验,确不知道 strncpy 会把剩下的部分全置为 0(粗体部分)。

char *strncpy(char *dest, const char *src, size_t n);

DESCRIPTION
The strcpy() function copies the string pointed to by src (including the terminating `\0′ character) to the array pointed to by dest. The strings may
not overlap, and the destination string dest must be large enough to receive the copy.
The strncpy() function is similar, except that not more than n bytes of src are copied. Thus, if there is no null byte among the first n bytes of src,
the result will not be null-terminated.
In the case where the length of src is less than that of n, the remainder of dest will be padded with null bytes.

这会导致什么后果呢?

首先,如果 strncpy 的长度填错了,比如比实际的长,那么就可能会把其他数据清 0 了。我就遇到过这个问题,在后来检查代码看到这个问题时,也并不以为然,因为拷贝的字符串不可能超过缓冲区的长度。

另外,假设 dest 的长度为 1024, 而待拷贝的字符串长度只有 24,strncpy 会把余下的 1000 各字节全部置为 0. 这就可能会导致性能问题,这也是我为什么说 strncpy 是低效的。

误解二:如果src 长度大于等于 n, 那么 strncpy 会拷贝 n – 1 各字符到 dest, 然后补 0?

错,大错特错,罚抄上面的 DESCRIPTION ,直到看到:

if there is no null byte among the first n bytes of src, the result will not be null-terminated.

这就可能导致了不安全的因素。

如果待拷贝字符串长度大于了 n, 那么 dest 是不会有结尾字符 0 的。假设这样一种情况:

    char s[] = "hello world";
    strncpy(s, "shit!", 5);
    puts(s);

 

输出的结果是 “shit” 还是 “shit! world” ? (输出后者),如果拷贝6个,就是前者。

这种情况只是导致了输出结果错误,严重的,如果 dest n 字节后面一直没有 0,那么就会导致程序段错误。

strncpy 最开始引入标准库是用来处理结构体中固定长度的字符串,比如路径名,而这些字符串的用法不同于 C 中带结尾字 0 的字符串。所以 strncpy 的初衷并不是一个安全的 strcpy.

 

那么用那些函数来替代 strncpy?

1、使用 snprintf

snprintf(dest, n, src);

的效果和我们对一个安全的字符串拷贝函数的期望完全一致。

但是这个函数效率有点问题,并且特殊字符比如 %d 会转义。

2、自己实现一个高效并且安全的字符串拷贝函数 sstrncpy,开头的 s 代表 safe

/* safe strncpy */

char *sstrncpy(char *dest, const char *src, size_t n)
{
    if (n == 0)
        return dest;

    dest[0] = 0;

    return strncat(dest, src, n - 1);
}

 

使用 strncat 是因为很难实现一个性能能够达到库函数的字符串拷贝函数。

3、但是,上面两个函数都有一个问题:如果不能预知 src 的最大长度,那么 src 会被静默的截断。

如果是为了复制一个字符串,那么更好的做法是使用 strdup 函数

char * strdup (const char *s);

strdup 函数会调用 malloc 分配足够长度的内存并返回。

当然,你需要在你不使用的时候 free 它。

如果只是函数内部调用,也可以使用 strdupa 函数。

char * strdupa (const char *s);

strdupa 函数调用 alloca函数而非 malloc 函数分配内存,alloca 分配的内存是桟内存而非堆内存。所以当函数返回后,内存就自动释放了,不需要 free。

4、如果是从文本文件中读数据,相对与 fgets 函数,更好的做法是使用 getline

ssize_t getline (char **lineptr, size_t *n, FILE *stream);

 

一个简单的例子:
char *line = NULL;
size_t len = 0;

while (getline(&amp;line, &amp;len, stdin) != -1)
{
    fputs(line, stdout);
}

free(line);

 

 

当 line 为 NULL 或者 len 为 0 时,getline 调用 malloc 分配足够大的内存。所以你需要在用完后 free 它们。

和 fgets 相同,getline 得到的行是带换行字符的。

所以,忘了 strncpy 吧,血的教训,说出来都是泪…

发表评论

电子邮件地址不会被公开。 必填项已用*标注