Error[8]: Undefined offset: 695, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

0.问题来源:

开发基于C/C++的底层程序,希望0依赖,跨平台,用语言自带的函数进行字符串的处理,主要涉及到strcpy、strcat、sprintf函数,这里暂且只讨论sprintf函数。


1.sprintf函数(Windows/Linux)

我们知道,C/C++里面最常用的字符串格式化函数是sprintf。但是这个函数最直接的问题是可能导致字符串越界的问题。如下面例子,因为预先无法预测格式化字符串%s对应的字符串str的长度,而缓存buf的长度是固定的,就有可能会把buf写越界。

#include 
#include 
#include 

int main()
{
    char buf[8] = {0};
    char *str = "890";
    sprintf(buf, "1234567%s\n", str);
    printf("%s\n", buf);
    return 0;
}

在windows上,IDE会产生警告,提示使用sprintf_s函数:

运行的时候直接会抛出异常。

linux上,程序运行后格式化后的字符串超过原始缓存的大小,也就是越界了,即“1234567890”。
我们实际希望的是,宁愿字符串被截断了,也不要被写越界了。

所以在window和linux上都不推荐使用sprintf函数。

针对这个问题,在windows上推荐使用sprintf_s函数。在调用的时候支持用户传入目标内存的长度,函数原型可以简略的表示为:
int sprintf_s(char *buf, size_t buf_size, const char *format, …);

2.sprintf_s函数(Windows)

sprintf_s函数似乎更好,但是这个函数的问题是:
1.Debug模式下会触发断言

2.在Release模式下会导致程序直接core掉,如下面程序只打印出sprintf_s函数直接的内容 before sprintf_s…,后面就没有了。

3.在跨平台的场合,在linux上没法用。

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123456";
    printf("\nbefore sprintf_s...");
    sprintf_s(buf, sizeof(buf), "%s", p);
    printf("\nafter sprintf_s...");
}

注意到sprintf_s函数的第二个参数增加了缓存大小这个参数,自然出现一个snprintf函数。

微软也许是觉得sprintf_s也不够好,MSVC环境中还引入了一个名为_snprintf的函数,其函数原型和sprintf_s类似,可以表示为:
int _snprintf(char *buf, size_t buf_size, const char *format, …);

3._snprintf函数(Windows)

这个函数虽然字符串越界(内存不会被破坏),不会触发断言和退出,但是也不太好用。
其最大的问题是不会自动在末尾加\0(不知道微软程序员怎么想的,不符合常规思维)。

_snprintf函数正常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 3,buf = 123

_snprintf函数异常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "12345";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 5,buf = 12345烫烫烫烫烫烫烫贪洬Y?

当然,在Linux上,有一个snprintf函数,做的比较完美:
1.不会触发断言
2.不会溢出破坏内存
3.保证结尾为\0

4._snprintf_s函数(Windows)

另外,微软还提供了一个越来越复杂的_snprintf_s函数,姑且先不关心其用法,至少在linux上无法用这个函数,也就做不到跨平台,所以也就不具体讨论了。

5.snprintf函数(Windows/Linux)

为了解决这个sprintf格式化字符串的终极问题,实现跨平台,不少地方有这样的做法:

#  define snprintf _snprintf

这个在早期的确解决了windows上_snprintf()和linux上snprintf()函数名不同的问题,实现跨平台,但是之前也说了,windows上_snprintf()函数不保证\0结尾,所以上面的#define 的方式实现跨平台其实也是不安全的。

或许是微软良心发现,或许是微软回归正道,终于在VS2015里面提供了C99标准里面规定的snprint函数,这样就和linux下能统一了。可惜1999~2000年推出的C99标准,微软硬是到VS2015才去实现这个函数。

https://docs.microsoft.com/en-us/previous-versions/hh409293(v=vs.140)?redirectedfrom=MSDN




我们在Windows和Linux上分别用snprintf函数进行测试:

#include 
#include 
#include 

int main()
{
    char desc[8];

    memset(desc, '*', sizeof(desc));
    int ret = snprintf(desc, sizeof(desc), "%s", "12345");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "12345678");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "1234567890");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    return 0;
}

Windows上的结果为

[1] [2] [3] [4] [5] [] [*] [*]   ret = 5
[1] [2] [3] [4] [5] [6] [7] []   ret = 8
[1] [2] [3] [4] [5] [6] [7] []   ret = 10

Linux上的结果为

[1] [2] [3] [4] [5] [] [*] [*] 	 ret = 5
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 8
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 10

由此可见,在VS2015后,snprintf函数在Windows和Linux上都可用了,而且都能\0结尾,返回值的行为也一样。

6.strcpy/strcat函数(Windows/Linux)

同样strcpy/strcat也一堆函数,坑多还不好用,或者不跨平台,所以干脆就把glibc里面的函数拿过来跨平台用算了。

//====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcpy.c
//====================================================
/*
 * Copy string src to buffer dst of size dsize.  At most dsize-1
 * chars will be copied.  Always NUL terminates (unless dsize == 0).
 * Returns strlen(src); if retval >= dsize, truncation occurred.
 */
size_t strlcpy(char *dst, const char *src, size_t dsize)
{
    const char *osrc = src;
    size_t nleft = dsize;

    /* Copy as many bytes as will fit. */
    if (nleft != 0)
    {
        while (--nleft != 0)
        {
            if ((*dst++ = *src++) == ')'break
                ;}
        }
    /* Not enough room in dst, add NUL and traverse rest of src. */

    if
    ( ==nleft 0 )if
    {
        ( !=dsize 0 )*
            =dst ';' /* NUL-terminate dst */while (
        * ++)src;}
            return
    (

    - -src 1 osrc ) ;/* count does not include NUL */} //====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcat.c
//====================================================
size_t
strlcat
/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
( char*, constdstchar * , size_tsrc) char siz*
{
    = ;d const dstchar
    * = ;s size_t src=
    ; n size_t siz;
    /* Find the end of dst and adjust bytes left but don't go past end */ dlenwhile

    (
    -- !=n0 && * != ')'d ++ ;=
        d-;
    dlen = d - dst;
    n if siz ( dlen==

    0 )n return (+
        strlen (dlen ) );swhile(*
    != ')'ifs ( !=1
    {
        ) *n ++ =*
        {
            ;d-- ; }s++
            n;}
        *
        s=';'
    return
    (d + (-

    ) )dlen ; /* count does not include NUL */s } src[+++][+++][+++] [+++]
[+++]
7.后记

搞C/C++开发太不容易了,连最基础的字符串处理函数都一堆坑,一堆差异,而且很多都是教科书上没讲的,加上考虑最近注意到的不同 *** 作系统上localtime函数的坑,可重入、线程安全的使用注意问题,以及C++11被C++之父都说可以看成一门新的语言,所以搞C++开发的人真不容易。

最近还听说少儿编程大多数是学Python,极少数的学C++,相比Python,C++更枯燥。这些学C++的都是为了冲刺信奥赛的算法比赛的。

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 29, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 696, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

0.问题来源:

开发基于C/C++的底层程序,希望0依赖,跨平台,用语言自带的函数进行字符串的处理,主要涉及到strcpy、strcat、sprintf函数,这里暂且只讨论sprintf函数。


1.sprintf函数(Windows/Linux)

我们知道,C/C++里面最常用的字符串格式化函数是sprintf。但是这个函数最直接的问题是可能导致字符串越界的问题。如下面例子,因为预先无法预测格式化字符串%s对应的字符串str的长度,而缓存buf的长度是固定的,就有可能会把buf写越界。

#include 
#include 
#include 

int main()
{
    char buf[8] = {0};
    char *str = "890";
    sprintf(buf, "1234567%s\n", str);
    printf("%s\n", buf);
    return 0;
}

在windows上,IDE会产生警告,提示使用sprintf_s函数:

运行的时候直接会抛出异常。

linux上,程序运行后格式化后的字符串超过原始缓存的大小,也就是越界了,即“1234567890”。
我们实际希望的是,宁愿字符串被截断了,也不要被写越界了。

所以在window和linux上都不推荐使用sprintf函数。

针对这个问题,在windows上推荐使用sprintf_s函数。在调用的时候支持用户传入目标内存的长度,函数原型可以简略的表示为:
int sprintf_s(char *buf, size_t buf_size, const char *format, …);

2.sprintf_s函数(Windows)

sprintf_s函数似乎更好,但是这个函数的问题是:
1.Debug模式下会触发断言

2.在Release模式下会导致程序直接core掉,如下面程序只打印出sprintf_s函数直接的内容 before sprintf_s…,后面就没有了。

3.在跨平台的场合,在linux上没法用。

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123456";
    printf("\nbefore sprintf_s...");
    sprintf_s(buf, sizeof(buf), "%s", p);
    printf("\nafter sprintf_s...");
}

注意到sprintf_s函数的第二个参数增加了缓存大小这个参数,自然出现一个snprintf函数。

微软也许是觉得sprintf_s也不够好,MSVC环境中还引入了一个名为_snprintf的函数,其函数原型和sprintf_s类似,可以表示为:
int _snprintf(char *buf, size_t buf_size, const char *format, …);

3._snprintf函数(Windows)

这个函数虽然字符串越界(内存不会被破坏),不会触发断言和退出,但是也不太好用。
其最大的问题是不会自动在末尾加\0(不知道微软程序员怎么想的,不符合常规思维)。

_snprintf函数正常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 3,buf = 123

_snprintf函数异常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "12345";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 5,buf = 12345烫烫烫烫烫烫烫贪洬Y?

当然,在Linux上,有一个snprintf函数,做的比较完美:
1.不会触发断言
2.不会溢出破坏内存
3.保证结尾为\0

4._snprintf_s函数(Windows)

另外,微软还提供了一个越来越复杂的_snprintf_s函数,姑且先不关心其用法,至少在linux上无法用这个函数,也就做不到跨平台,所以也就不具体讨论了。

5.snprintf函数(Windows/Linux)

为了解决这个sprintf格式化字符串的终极问题,实现跨平台,不少地方有这样的做法:

#  define snprintf _snprintf

这个在早期的确解决了windows上_snprintf()和linux上snprintf()函数名不同的问题,实现跨平台,但是之前也说了,windows上_snprintf()函数不保证\0结尾,所以上面的#define 的方式实现跨平台其实也是不安全的。

或许是微软良心发现,或许是微软回归正道,终于在VS2015里面提供了C99标准里面规定的snprint函数,这样就和linux下能统一了。可惜1999~2000年推出的C99标准,微软硬是到VS2015才去实现这个函数。

https://docs.microsoft.com/en-us/previous-versions/hh409293(v=vs.140)?redirectedfrom=MSDN




我们在Windows和Linux上分别用snprintf函数进行测试:

#include 
#include 
#include 

int main()
{
    char desc[8];

    memset(desc, '*', sizeof(desc));
    int ret = snprintf(desc, sizeof(desc), "%s", "12345");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "12345678");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "1234567890");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    return 0;
}

Windows上的结果为

[1] [2] [3] [4] [5] [] [*] [*]   ret = 5
[1] [2] [3] [4] [5] [6] [7] []   ret = 8
[1] [2] [3] [4] [5] [6] [7] []   ret = 10

Linux上的结果为

[1] [2] [3] [4] [5] [] [*] [*] 	 ret = 5
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 8
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 10

由此可见,在VS2015后,snprintf函数在Windows和Linux上都可用了,而且都能\0结尾,返回值的行为也一样。

6.strcpy/strcat函数(Windows/Linux)

同样strcpy/strcat也一堆函数,坑多还不好用,或者不跨平台,所以干脆就把glibc里面的函数拿过来跨平台用算了。

//====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcpy.c
//====================================================
/*
 * Copy string src to buffer dst of size dsize.  At most dsize-1
 * chars will be copied.  Always NUL terminates (unless dsize == 0).
 * Returns strlen(src); if retval >= dsize, truncation occurred.
 */
size_t strlcpy(char *dst, const char *src, size_t dsize)
{
    const char *osrc = src;
    size_t nleft = dsize;

    /* Copy as many bytes as will fit. */
    if (nleft != 0)
    {
        while (--nleft != 0)
        {
            if ((*dst++ = *src++) == ')'break
                ;}
        }
    /* Not enough room in dst, add NUL and traverse rest of src. */

    if
    ( ==nleft 0 )if
    {
        ( !=dsize 0 )*
            =dst ';' /* NUL-terminate dst */while (
        * ++)src;}
            return
    (

    - -src 1 osrc ) ;/* count does not include NUL */} //====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcat.c
//====================================================
size_t
strlcat
/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
( char*, constdstchar * , size_tsrc) char siz*
{
    = ;d const dstchar
    * = ;s size_t src=
    ; n size_t siz;
    /* Find the end of dst and adjust bytes left but don't go past end */ dlenwhile

    (
    -- !=n0 && * != ')'d ++ ;=
        d-;
    dlen = d - dst;
    n if siz ( dlen==

    0 )n return (+
        strlen (dlen ) );swhile(*
    != ')'ifs ( !=1
    {
        ) *n ++ =*
        {
            ;d-- ; }s++
            n;}
        *
        s=';'
    return
    (d + (-

    ) )dlen ; /* count does not include NUL */s } src[+++][+++] [+++]
[+++]
7.后记

搞C/C++开发太不容易了,连最基础的字符串处理函数都一堆坑,一堆差异,而且很多都是教科书上没讲的,加上考虑最近注意到的不同 *** 作系统上localtime函数的坑,可重入、线程安全的使用注意问题,以及C++11被C++之父都说可以看成一门新的语言,所以搞C++开发的人真不容易。

最近还听说少儿编程大多数是学Python,极少数的学C++,相比Python,C++更枯燥。这些学C++的都是为了冲刺信奥赛的算法比赛的。

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 29, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 697, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

0.问题来源:

开发基于C/C++的底层程序,希望0依赖,跨平台,用语言自带的函数进行字符串的处理,主要涉及到strcpy、strcat、sprintf函数,这里暂且只讨论sprintf函数。


1.sprintf函数(Windows/Linux)

我们知道,C/C++里面最常用的字符串格式化函数是sprintf。但是这个函数最直接的问题是可能导致字符串越界的问题。如下面例子,因为预先无法预测格式化字符串%s对应的字符串str的长度,而缓存buf的长度是固定的,就有可能会把buf写越界。

#include 
#include 
#include 

int main()
{
    char buf[8] = {0};
    char *str = "890";
    sprintf(buf, "1234567%s\n", str);
    printf("%s\n", buf);
    return 0;
}

在windows上,IDE会产生警告,提示使用sprintf_s函数:

运行的时候直接会抛出异常。

linux上,程序运行后格式化后的字符串超过原始缓存的大小,也就是越界了,即“1234567890”。
我们实际希望的是,宁愿字符串被截断了,也不要被写越界了。

所以在window和linux上都不推荐使用sprintf函数。

针对这个问题,在windows上推荐使用sprintf_s函数。在调用的时候支持用户传入目标内存的长度,函数原型可以简略的表示为:
int sprintf_s(char *buf, size_t buf_size, const char *format, …);

2.sprintf_s函数(Windows)

sprintf_s函数似乎更好,但是这个函数的问题是:
1.Debug模式下会触发断言

2.在Release模式下会导致程序直接core掉,如下面程序只打印出sprintf_s函数直接的内容 before sprintf_s…,后面就没有了。

3.在跨平台的场合,在linux上没法用。

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123456";
    printf("\nbefore sprintf_s...");
    sprintf_s(buf, sizeof(buf), "%s", p);
    printf("\nafter sprintf_s...");
}

注意到sprintf_s函数的第二个参数增加了缓存大小这个参数,自然出现一个snprintf函数。

微软也许是觉得sprintf_s也不够好,MSVC环境中还引入了一个名为_snprintf的函数,其函数原型和sprintf_s类似,可以表示为:
int _snprintf(char *buf, size_t buf_size, const char *format, …);

3._snprintf函数(Windows)

这个函数虽然字符串越界(内存不会被破坏),不会触发断言和退出,但是也不太好用。
其最大的问题是不会自动在末尾加\0(不知道微软程序员怎么想的,不符合常规思维)。

_snprintf函数正常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 3,buf = 123

_snprintf函数异常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "12345";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 5,buf = 12345烫烫烫烫烫烫烫贪洬Y?

当然,在Linux上,有一个snprintf函数,做的比较完美:
1.不会触发断言
2.不会溢出破坏内存
3.保证结尾为\0

4._snprintf_s函数(Windows)

另外,微软还提供了一个越来越复杂的_snprintf_s函数,姑且先不关心其用法,至少在linux上无法用这个函数,也就做不到跨平台,所以也就不具体讨论了。

5.snprintf函数(Windows/Linux)

为了解决这个sprintf格式化字符串的终极问题,实现跨平台,不少地方有这样的做法:

#  define snprintf _snprintf

这个在早期的确解决了windows上_snprintf()和linux上snprintf()函数名不同的问题,实现跨平台,但是之前也说了,windows上_snprintf()函数不保证\0结尾,所以上面的#define 的方式实现跨平台其实也是不安全的。

或许是微软良心发现,或许是微软回归正道,终于在VS2015里面提供了C99标准里面规定的snprint函数,这样就和linux下能统一了。可惜1999~2000年推出的C99标准,微软硬是到VS2015才去实现这个函数。

https://docs.microsoft.com/en-us/previous-versions/hh409293(v=vs.140)?redirectedfrom=MSDN




我们在Windows和Linux上分别用snprintf函数进行测试:

#include 
#include 
#include 

int main()
{
    char desc[8];

    memset(desc, '*', sizeof(desc));
    int ret = snprintf(desc, sizeof(desc), "%s", "12345");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "12345678");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "1234567890");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    return 0;
}

Windows上的结果为

[1] [2] [3] [4] [5] [] [*] [*]   ret = 5
[1] [2] [3] [4] [5] [6] [7] []   ret = 8
[1] [2] [3] [4] [5] [6] [7] []   ret = 10

Linux上的结果为

[1] [2] [3] [4] [5] [] [*] [*] 	 ret = 5
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 8
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 10

由此可见,在VS2015后,snprintf函数在Windows和Linux上都可用了,而且都能\0结尾,返回值的行为也一样。

6.strcpy/strcat函数(Windows/Linux)

同样strcpy/strcat也一堆函数,坑多还不好用,或者不跨平台,所以干脆就把glibc里面的函数拿过来跨平台用算了。

//====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcpy.c
//====================================================
/*
 * Copy string src to buffer dst of size dsize.  At most dsize-1
 * chars will be copied.  Always NUL terminates (unless dsize == 0).
 * Returns strlen(src); if retval >= dsize, truncation occurred.
 */
size_t strlcpy(char *dst, const char *src, size_t dsize)
{
    const char *osrc = src;
    size_t nleft = dsize;

    /* Copy as many bytes as will fit. */
    if (nleft != 0)
    {
        while (--nleft != 0)
        {
            if ((*dst++ = *src++) == ')'break
                ;}
        }
    /* Not enough room in dst, add NUL and traverse rest of src. */

    if
    ( ==nleft 0 )if
    {
        ( !=dsize 0 )*
            =dst ';' /* NUL-terminate dst */while (
        * ++)src;}
            return
    (

    - -src 1 osrc ) ;/* count does not include NUL */} //====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcat.c
//====================================================
size_t
strlcat
/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
( char*, constdstchar * , size_tsrc) char siz*
{
    = ;d const dstchar
    * = ;s size_t src=
    ; n size_t siz;
    /* Find the end of dst and adjust bytes left but don't go past end */ dlenwhile

    (
    -- !=n0 && * != ')'d ++ ;=
        d-;
    dlen = d - dst;
    n if siz ( dlen==

    0 )n return (+
        strlen (dlen ) );swhile(*
    != ')'ifs ( !=1
    {
        ) *n ++ =*
        {
            ;d-- ; }s++
            n;}
        *
        s=';'
    return
    (d + (-

    ) )dlen ; /* count does not include NUL */s } src[+++] [+++]
[+++]
7.后记

搞C/C++开发太不容易了,连最基础的字符串处理函数都一堆坑,一堆差异,而且很多都是教科书上没讲的,加上考虑最近注意到的不同 *** 作系统上localtime函数的坑,可重入、线程安全的使用注意问题,以及C++11被C++之父都说可以看成一门新的语言,所以搞C++开发的人真不容易。

最近还听说少儿编程大多数是学Python,极少数的学C++,相比Python,C++更枯燥。这些学C++的都是为了冲刺信奥赛的算法比赛的。

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 29, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 698, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

0.问题来源:

开发基于C/C++的底层程序,希望0依赖,跨平台,用语言自带的函数进行字符串的处理,主要涉及到strcpy、strcat、sprintf函数,这里暂且只讨论sprintf函数。


1.sprintf函数(Windows/Linux)

我们知道,C/C++里面最常用的字符串格式化函数是sprintf。但是这个函数最直接的问题是可能导致字符串越界的问题。如下面例子,因为预先无法预测格式化字符串%s对应的字符串str的长度,而缓存buf的长度是固定的,就有可能会把buf写越界。

#include 
#include 
#include 

int main()
{
    char buf[8] = {0};
    char *str = "890";
    sprintf(buf, "1234567%s\n", str);
    printf("%s\n", buf);
    return 0;
}

在windows上,IDE会产生警告,提示使用sprintf_s函数:

运行的时候直接会抛出异常。

linux上,程序运行后格式化后的字符串超过原始缓存的大小,也就是越界了,即“1234567890”。
我们实际希望的是,宁愿字符串被截断了,也不要被写越界了。

所以在window和linux上都不推荐使用sprintf函数。

针对这个问题,在windows上推荐使用sprintf_s函数。在调用的时候支持用户传入目标内存的长度,函数原型可以简略的表示为:
int sprintf_s(char *buf, size_t buf_size, const char *format, …);

2.sprintf_s函数(Windows)

sprintf_s函数似乎更好,但是这个函数的问题是:
1.Debug模式下会触发断言

2.在Release模式下会导致程序直接core掉,如下面程序只打印出sprintf_s函数直接的内容 before sprintf_s…,后面就没有了。

3.在跨平台的场合,在linux上没法用。

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123456";
    printf("\nbefore sprintf_s...");
    sprintf_s(buf, sizeof(buf), "%s", p);
    printf("\nafter sprintf_s...");
}

注意到sprintf_s函数的第二个参数增加了缓存大小这个参数,自然出现一个snprintf函数。

微软也许是觉得sprintf_s也不够好,MSVC环境中还引入了一个名为_snprintf的函数,其函数原型和sprintf_s类似,可以表示为:
int _snprintf(char *buf, size_t buf_size, const char *format, …);

3._snprintf函数(Windows)

这个函数虽然字符串越界(内存不会被破坏),不会触发断言和退出,但是也不太好用。
其最大的问题是不会自动在末尾加\0(不知道微软程序员怎么想的,不符合常规思维)。

_snprintf函数正常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 3,buf = 123

_snprintf函数异常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "12345";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 5,buf = 12345烫烫烫烫烫烫烫贪洬Y?

当然,在Linux上,有一个snprintf函数,做的比较完美:
1.不会触发断言
2.不会溢出破坏内存
3.保证结尾为\0

4._snprintf_s函数(Windows)

另外,微软还提供了一个越来越复杂的_snprintf_s函数,姑且先不关心其用法,至少在linux上无法用这个函数,也就做不到跨平台,所以也就不具体讨论了。

5.snprintf函数(Windows/Linux)

为了解决这个sprintf格式化字符串的终极问题,实现跨平台,不少地方有这样的做法:

#  define snprintf _snprintf

这个在早期的确解决了windows上_snprintf()和linux上snprintf()函数名不同的问题,实现跨平台,但是之前也说了,windows上_snprintf()函数不保证\0结尾,所以上面的#define 的方式实现跨平台其实也是不安全的。

或许是微软良心发现,或许是微软回归正道,终于在VS2015里面提供了C99标准里面规定的snprint函数,这样就和linux下能统一了。可惜1999~2000年推出的C99标准,微软硬是到VS2015才去实现这个函数。

https://docs.microsoft.com/en-us/previous-versions/hh409293(v=vs.140)?redirectedfrom=MSDN




我们在Windows和Linux上分别用snprintf函数进行测试:

#include 
#include 
#include 

int main()
{
    char desc[8];

    memset(desc, '*', sizeof(desc));
    int ret = snprintf(desc, sizeof(desc), "%s", "12345");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "12345678");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "1234567890");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    return 0;
}

Windows上的结果为

[1] [2] [3] [4] [5] [] [*] [*]   ret = 5
[1] [2] [3] [4] [5] [6] [7] []   ret = 8
[1] [2] [3] [4] [5] [6] [7] []   ret = 10

Linux上的结果为

[1] [2] [3] [4] [5] [] [*] [*] 	 ret = 5
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 8
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 10

由此可见,在VS2015后,snprintf函数在Windows和Linux上都可用了,而且都能\0结尾,返回值的行为也一样。

6.strcpy/strcat函数(Windows/Linux)

同样strcpy/strcat也一堆函数,坑多还不好用,或者不跨平台,所以干脆就把glibc里面的函数拿过来跨平台用算了。

//====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcpy.c
//====================================================
/*
 * Copy string src to buffer dst of size dsize.  At most dsize-1
 * chars will be copied.  Always NUL terminates (unless dsize == 0).
 * Returns strlen(src); if retval >= dsize, truncation occurred.
 */
size_t strlcpy(char *dst, const char *src, size_t dsize)
{
    const char *osrc = src;
    size_t nleft = dsize;

    /* Copy as many bytes as will fit. */
    if (nleft != 0)
    {
        while (--nleft != 0)
        {
            if ((*dst++ = *src++) == ')'break
                ;}
        }
    /* Not enough room in dst, add NUL and traverse rest of src. */

    if
    ( ==nleft 0 )if
    {
        ( !=dsize 0 )*
            =dst ';' /* NUL-terminate dst */while (
        * ++)src;}
            return
    (

    - -src 1 osrc ) ;/* count does not include NUL */} //====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcat.c
//====================================================
size_t
strlcat
/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
( char*, constdstchar * , size_tsrc) char siz*
{
    = ;d const dstchar
    * = ;s size_t src=
    ; n size_t siz;
    /* Find the end of dst and adjust bytes left but don't go past end */ dlenwhile

    (
    -- !=n0 && * != ')'d ++ ;=
        d-;
    dlen = d - dst;
    n if siz ( dlen==

    0 )n return (+
        strlen (dlen ) );swhile(*
    != ')'ifs ( !=1
    {
        ) *n ++ =*
        {
            ;d-- ; }s++
            n;}
        *
        s=';'
    return
    (d + (-

    ) )dlen ; /* count does not include NUL */s } src [+++]
[+++]
7.后记

搞C/C++开发太不容易了,连最基础的字符串处理函数都一堆坑,一堆差异,而且很多都是教科书上没讲的,加上考虑最近注意到的不同 *** 作系统上localtime函数的坑,可重入、线程安全的使用注意问题,以及C++11被C++之父都说可以看成一门新的语言,所以搞C++开发的人真不容易。

最近还听说少儿编程大多数是学Python,极少数的学C++,相比Python,C++更枯燥。这些学C++的都是为了冲刺信奥赛的算法比赛的。

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 29, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 699, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

0.问题来源:

开发基于C/C++的底层程序,希望0依赖,跨平台,用语言自带的函数进行字符串的处理,主要涉及到strcpy、strcat、sprintf函数,这里暂且只讨论sprintf函数。


1.sprintf函数(Windows/Linux)

我们知道,C/C++里面最常用的字符串格式化函数是sprintf。但是这个函数最直接的问题是可能导致字符串越界的问题。如下面例子,因为预先无法预测格式化字符串%s对应的字符串str的长度,而缓存buf的长度是固定的,就有可能会把buf写越界。

#include 
#include 
#include 

int main()
{
    char buf[8] = {0};
    char *str = "890";
    sprintf(buf, "1234567%s\n", str);
    printf("%s\n", buf);
    return 0;
}

在windows上,IDE会产生警告,提示使用sprintf_s函数:

运行的时候直接会抛出异常。

linux上,程序运行后格式化后的字符串超过原始缓存的大小,也就是越界了,即“1234567890”。
我们实际希望的是,宁愿字符串被截断了,也不要被写越界了。

所以在window和linux上都不推荐使用sprintf函数。

针对这个问题,在windows上推荐使用sprintf_s函数。在调用的时候支持用户传入目标内存的长度,函数原型可以简略的表示为:
int sprintf_s(char *buf, size_t buf_size, const char *format, …);

2.sprintf_s函数(Windows)

sprintf_s函数似乎更好,但是这个函数的问题是:
1.Debug模式下会触发断言

2.在Release模式下会导致程序直接core掉,如下面程序只打印出sprintf_s函数直接的内容 before sprintf_s…,后面就没有了。

3.在跨平台的场合,在linux上没法用。

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123456";
    printf("\nbefore sprintf_s...");
    sprintf_s(buf, sizeof(buf), "%s", p);
    printf("\nafter sprintf_s...");
}

注意到sprintf_s函数的第二个参数增加了缓存大小这个参数,自然出现一个snprintf函数。

微软也许是觉得sprintf_s也不够好,MSVC环境中还引入了一个名为_snprintf的函数,其函数原型和sprintf_s类似,可以表示为:
int _snprintf(char *buf, size_t buf_size, const char *format, …);

3._snprintf函数(Windows)

这个函数虽然字符串越界(内存不会被破坏),不会触发断言和退出,但是也不太好用。
其最大的问题是不会自动在末尾加\0(不知道微软程序员怎么想的,不符合常规思维)。

_snprintf函数正常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 3,buf = 123

_snprintf函数异常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "12345";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 5,buf = 12345烫烫烫烫烫烫烫贪洬Y?

当然,在Linux上,有一个snprintf函数,做的比较完美:
1.不会触发断言
2.不会溢出破坏内存
3.保证结尾为\0

4._snprintf_s函数(Windows)

另外,微软还提供了一个越来越复杂的_snprintf_s函数,姑且先不关心其用法,至少在linux上无法用这个函数,也就做不到跨平台,所以也就不具体讨论了。

5.snprintf函数(Windows/Linux)

为了解决这个sprintf格式化字符串的终极问题,实现跨平台,不少地方有这样的做法:

#  define snprintf _snprintf

这个在早期的确解决了windows上_snprintf()和linux上snprintf()函数名不同的问题,实现跨平台,但是之前也说了,windows上_snprintf()函数不保证\0结尾,所以上面的#define 的方式实现跨平台其实也是不安全的。

或许是微软良心发现,或许是微软回归正道,终于在VS2015里面提供了C99标准里面规定的snprint函数,这样就和linux下能统一了。可惜1999~2000年推出的C99标准,微软硬是到VS2015才去实现这个函数。

https://docs.microsoft.com/en-us/previous-versions/hh409293(v=vs.140)?redirectedfrom=MSDN




我们在Windows和Linux上分别用snprintf函数进行测试:

#include 
#include 
#include 

int main()
{
    char desc[8];

    memset(desc, '*', sizeof(desc));
    int ret = snprintf(desc, sizeof(desc), "%s", "12345");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "12345678");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "1234567890");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    return 0;
}

Windows上的结果为

[1] [2] [3] [4] [5] [] [*] [*]   ret = 5
[1] [2] [3] [4] [5] [6] [7] []   ret = 8
[1] [2] [3] [4] [5] [6] [7] []   ret = 10

Linux上的结果为

[1] [2] [3] [4] [5] [] [*] [*] 	 ret = 5
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 8
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 10

由此可见,在VS2015后,snprintf函数在Windows和Linux上都可用了,而且都能\0结尾,返回值的行为也一样。

6.strcpy/strcat函数(Windows/Linux)

同样strcpy/strcat也一堆函数,坑多还不好用,或者不跨平台,所以干脆就把glibc里面的函数拿过来跨平台用算了。

//====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcpy.c
//====================================================
/*
 * Copy string src to buffer dst of size dsize.  At most dsize-1
 * chars will be copied.  Always NUL terminates (unless dsize == 0).
 * Returns strlen(src); if retval >= dsize, truncation occurred.
 */
size_t strlcpy(char *dst, const char *src, size_t dsize)
{
    const char *osrc = src;
    size_t nleft = dsize;

    /* Copy as many bytes as will fit. */
    if (nleft != 0)
    {
        while (--nleft != 0)
        {
            if ((*dst++ = *src++) == ')'break
                ;}
        }
    /* Not enough room in dst, add NUL and traverse rest of src. */

    if
    ( ==nleft 0 )if
    {
        ( !=dsize 0 )*
            =dst ';' /* NUL-terminate dst */while (
        * ++)src;}
            return
    (

    - -src 1 osrc ) ;/* count does not include NUL */} //====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcat.c
//====================================================
size_t
strlcat
/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
( char*, constdstchar * , size_tsrc) char siz*
{
    = ;d const dstchar
    * = ;s size_t src=
    ; n size_t siz;
    /* Find the end of dst and adjust bytes left but don't go past end */ dlenwhile

    (
    -- !=n0 && * != ')'d ++ ;=
        d-;
    dlen = d - dst;
    n if siz ( dlen==

    0 )n return (+
        strlen (dlen ) );swhile(*
    != ')'ifs ( !=1
    {
        ) *n ++ =*
        {
            ;d-- ; }s++
            n;}
        *
        s=';'
    return
    (d + (-

    ) )dlen ; /* count does not include NUL */s } src 
[+++]
7.后记

搞C/C++开发太不容易了,连最基础的字符串处理函数都一堆坑,一堆差异,而且很多都是教科书上没讲的,加上考虑最近注意到的不同 *** 作系统上localtime函数的坑,可重入、线程安全的使用注意问题,以及C++11被C++之父都说可以看成一门新的语言,所以搞C++开发的人真不容易。

最近还听说少儿编程大多数是学Python,极少数的学C++,相比Python,C++更枯燥。这些学C++的都是为了冲刺信奥赛的算法比赛的。

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 29, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
格式化字符串函数sprintf、snprintf的探索_C_内存溢出

格式化字符串函数sprintf、snprintf的探索

格式化字符串函数sprintf、snprintf的探索,第1张

0.问题来源:

开发基于C/C++的底层程序,希望0依赖,跨平台,用语言自带的函数进行字符串的处理,主要涉及到strcpy、strcat、sprintf函数,这里暂且只讨论sprintf函数。


1.sprintf函数(Windows/Linux)

我们知道,C/C++里面最常用的字符串格式化函数是sprintf。但是这个函数最直接的问题是可能导致字符串越界的问题。如下面例子,因为预先无法预测格式化字符串%s对应的字符串str的长度,而缓存buf的长度是固定的,就有可能会把buf写越界。

#include 
#include 
#include 

int main()
{
    char buf[8] = {0};
    char *str = "890";
    sprintf(buf, "1234567%s\n", str);
    printf("%s\n", buf);
    return 0;
}

在windows上,IDE会产生警告,提示使用sprintf_s函数:

运行的时候直接会抛出异常。

linux上,程序运行后格式化后的字符串超过原始缓存的大小,也就是越界了,即“1234567890”。
我们实际希望的是,宁愿字符串被截断了,也不要被写越界了。

所以在window和linux上都不推荐使用sprintf函数。

针对这个问题,在windows上推荐使用sprintf_s函数。在调用的时候支持用户传入目标内存的长度,函数原型可以简略的表示为:
int sprintf_s(char *buf, size_t buf_size, const char *format, …);

2.sprintf_s函数(Windows)

sprintf_s函数似乎更好,但是这个函数的问题是:
1.Debug模式下会触发断言

2.在Release模式下会导致程序直接core掉,如下面程序只打印出sprintf_s函数直接的内容 before sprintf_s…,后面就没有了。

3.在跨平台的场合,在linux上没法用。

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123456";
    printf("\nbefore sprintf_s...");
    sprintf_s(buf, sizeof(buf), "%s", p);
    printf("\nafter sprintf_s...");
}

注意到sprintf_s函数的第二个参数增加了缓存大小这个参数,自然出现一个snprintf函数。

微软也许是觉得sprintf_s也不够好,MSVC环境中还引入了一个名为_snprintf的函数,其函数原型和sprintf_s类似,可以表示为:
int _snprintf(char *buf, size_t buf_size, const char *format, …);

3._snprintf函数(Windows)

这个函数虽然字符串越界(内存不会被破坏),不会触发断言和退出,但是也不太好用。
其最大的问题是不会自动在末尾加\0(不知道微软程序员怎么想的,不符合常规思维)。

_snprintf函数正常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "123";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 3,buf = 123

_snprintf函数异常情况:

#include 
#include 
#include 

int main()
{
    char buf[5] = {0};
    const char *p = "12345";
    int ret = _snprintf(buf, sizeof(buf), "%s", p);
    printf("\nret = %d,buf = %s", ret, buf);
}
运行结果为:ret = 5,buf = 12345烫烫烫烫烫烫烫贪洬Y?

当然,在Linux上,有一个snprintf函数,做的比较完美:
1.不会触发断言
2.不会溢出破坏内存
3.保证结尾为\0

4._snprintf_s函数(Windows)

另外,微软还提供了一个越来越复杂的_snprintf_s函数,姑且先不关心其用法,至少在linux上无法用这个函数,也就做不到跨平台,所以也就不具体讨论了。

5.snprintf函数(Windows/Linux)

为了解决这个sprintf格式化字符串的终极问题,实现跨平台,不少地方有这样的做法:

#  define snprintf _snprintf

这个在早期的确解决了windows上_snprintf()和linux上snprintf()函数名不同的问题,实现跨平台,但是之前也说了,windows上_snprintf()函数不保证\0结尾,所以上面的#define 的方式实现跨平台其实也是不安全的。

或许是微软良心发现,或许是微软回归正道,终于在VS2015里面提供了C99标准里面规定的snprint函数,这样就和linux下能统一了。可惜1999~2000年推出的C99标准,微软硬是到VS2015才去实现这个函数。

https://docs.microsoft.com/en-us/previous-versions/hh409293(v=vs.140)?redirectedfrom=MSDN




我们在Windows和Linux上分别用snprintf函数进行测试:

#include 
#include 
#include 

int main()
{
    char desc[8];

    memset(desc, '*', sizeof(desc));
    int ret = snprintf(desc, sizeof(desc), "%s", "12345");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "12345678");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    memset(desc, '*', sizeof(desc));
    ret = snprintf(desc, sizeof(desc), "%s", "1234567890");
    for (int i = 0; i < sizeof(desc); i++)
        printf("[%c] ", desc[i]);
    printf("\t ret = %d\n", ret);

    return 0;
}

Windows上的结果为

[1] [2] [3] [4] [5] [] [*] [*]   ret = 5
[1] [2] [3] [4] [5] [6] [7] []   ret = 8
[1] [2] [3] [4] [5] [6] [7] []   ret = 10

Linux上的结果为

[1] [2] [3] [4] [5] [] [*] [*] 	 ret = 5
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 8
[1] [2] [3] [4] [5] [6] [7] [] 	 ret = 10

由此可见,在VS2015后,snprintf函数在Windows和Linux上都可用了,而且都能\0结尾,返回值的行为也一样。

6.strcpy/strcat函数(Windows/Linux)

同样strcpy/strcat也一堆函数,坑多还不好用,或者不跨平台,所以干脆就把glibc里面的函数拿过来跨平台用算了。

//====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcpy.c
//====================================================
/*
 * Copy string src to buffer dst of size dsize.  At most dsize-1
 * chars will be copied.  Always NUL terminates (unless dsize == 0).
 * Returns strlen(src); if retval >= dsize, truncation occurred.
 */
size_t strlcpy(char *dst, const char *src, size_t dsize)
{
    const char *osrc = src;
    size_t nleft = dsize;

    /* Copy as many bytes as will fit. */
    if (nleft != 0)
    {
        while (--nleft != 0)
        {
            if ((*dst++ = *src++) == ')'break
                ;}
        }
    /* Not enough room in dst, add NUL and traverse rest of src. */

    if
    ( ==nleft 0 )if
    {
        ( !=dsize 0 )*
            =dst ';' /* NUL-terminate dst */while (
        * ++)src;}
            return
    (

    - -src 1 osrc ) ;/* count does not include NUL */} //====================================================
//      strncpy
//  https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcat.c
//====================================================
size_t
strlcat
/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
( char*, constdstchar * , size_tsrc) char siz*
{
    = ;d const dstchar
    * = ;s size_t src=
    ; n size_t siz;
    /* Find the end of dst and adjust bytes left but don't go past end */ dlenwhile

    (
    -- !=n0 && * != ')'d ++ ;=
        d-;
    dlen = d - dst;
    n if siz ( dlen==

    0 )n return (+
        strlen (dlen ) );swhile(*
    != ')'ifs ( !=1
    {
        ) *n ++ =*
        {
            ;d-- ; }s++
            n;}
        *
        s=';'
    return
    (d + (-

    ) )dlen ; /* count does not include NUL */s } src 

7.后记

搞C/C++开发太不容易了,连最基础的字符串处理函数都一堆坑,一堆差异,而且很多都是教科书上没讲的,加上考虑最近注意到的不同 *** 作系统上localtime函数的坑,可重入、线程安全的使用注意问题,以及C++11被C++之父都说可以看成一门新的语言,所以搞C++开发的人真不容易。

最近还听说少儿编程大多数是学Python,极少数的学C++,相比Python,C++更枯燥。这些学C++的都是为了冲刺信奥赛的算法比赛的。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/872624.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存