strncpy 安全吗?

2020-04-12

测试一下看看,Linux 环境下,这三个函数(strcpy, strncpy, snprintf)哪个比较安全。


1. 测试代码

数据拷贝,当目标内存很小,源数据很大时,从测试结果看:

  • snprintf 结果正常,达到预期。
  • strcpy 拷贝的数据打印出来有点问题,不知道是否正常。
  • strncpy 崩溃了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// test.c
#include <stdio.h>
#include <string.h>

int main(int argc, char** argv) {
    char test[2];
    const char* p = "hello world";

    printf("%s", "snprintf: ");
    snprintf(test, sizeof(test), "%s", p);
    printf("%s\n", test);

    printf("%s", "strcpy: ");
    strcpy(test, p);
    printf("%s\n", test);

    printf("%s", "strncpy: ");
    strncpy(test, p, sizeof(test));
    printf("%s\n", test);
}

1
2
3
4
# gcc -g test.c -o test && ./test
snprintf: h
strcpy: hello world
[1]    18785 segmentation fault (core dumped)  ./test

2. 看源码,探究原因

可以查看 linux 内核源码或者 glibc 封装的代码。

  • strcpy

    从源码看,字符串拷贝是寻找 ‘\0’ 结束符,从上面的测试场景看,这个函数是不安全的。

1
2
3
4
5
6
7
// https://github.com/torvalds/linux/blob/master/lib/string.c
char *strcpy(char *dest, const char *src) {
    char *tmp = dest;
    while ((*dest++ = *src++) != '\0')
        /* nothing */;
    return tmp;
}
  • strncpy

    从源码看,字符串拷贝也是寻找 ‘\0’ 结束符,而且有数据大小限制。上面测试场景,数据拷贝是安全的,但是 printf 出问题了,因为 printf 在打印字符串时,也是在找字符串的 ‘\0’ 结束符。而 strncpy 不会自动在字符串末尾填充 ‘\0’。

    关于 printf,详细可以参考下我的帖子 printf 问题的思考

1
2
3
4
5
6
7
8
9
10
11
12
// https://github.com/torvalds/linux/blob/master/lib/string.c
char *strncpy(char *dest, const char *src, size_t count) {
    char *tmp = dest;

    while (count) {
        if ((*tmp = *src) != 0)
            src++;
        tmp++;
        count--;
    }
    return dest;
}
  • snprintf

    代码有点长,没仔细看完,从测试场景上看,是正常的。。。(不严谨啊。^_^!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* glibc 2.17 - snprintf.c */
#include <stdarg.h>
#include <stdio.h>
#include <libioP.h>
#define __vsnprintf(s, l, f, a) _IO_vsnprintf (s, l, f, a)

/* Write formatted output into S, according to the format
   string FORMAT, writing no more than MAXLEN characters.  */
/* VARARGS3 */
int
__snprintf (char *s, size_t maxlen, const char *format, ...)
{
  va_list arg;
  int done;

  va_start (arg, format);
  done = __vsnprintf (s, maxlen, format, arg);
  va_end (arg);

  return done;
}
ldbl_weak_alias (__snprintf, snprintf)


int vsnprintf(char *buf, size_t size, const char *fmt, va_list args) {
    ...
}

3. 总结

从测试结果看:

  • snprintf 比较安全。
  • strcpy 不安全。
  • strncpy 当目标内存很小时,拷贝完成后不会在末位填充 ‘\0’ ,拷贝操作后,目标字符串在使用中可能会有问题。

内存越界是 c/c++ 一个坑,有时候要解决这类型的偶发问题,只能看缘分。所以平时使用,须要形成良好的编码习惯。