精华内容
下载资源
问答
  • 字符串拷贝函数的安全用法模板

    千次阅读 2015-06-10 00:58:10
    字符串拷贝函数的安全用法模板 Src 完全拷贝 snprintf: bzero(dest, sizeof(dest)); snprintf(dest, sizeof(dest), “%s”, src); strncpy: bzero(dest, sizeof(dest)); strncpy(dest, src, sizeof...

    字符串拷贝函数的安全用法模板


    Src 完全拷贝
    snprintf:
    bzero(dest, sizeof(dest));
    snprintf(dest, sizeof(dest), “%s”, src);


    strncpy:

    bzero(dest, sizeof(dest));

    strncpy(dest, src, sizeof(dest)-1);


    Src 截取和完全拷贝
    memcpy:
    bzero(dest, sizeof(dest));
    len = dest_len < src_len ? dest_len : src_len -1;
    memcpy(dest, src, len);





    展开全文
  • 字符串拷贝函数

    2012-07-06 09:51:28
     这次面试腾讯,面试官要我写个字符串拷贝函数,头一天晚上一晚上没睡好,脑袋昏昏沉沉的,之前看过C++中的字符串拷贝构造函数,先入为主,也没多想就开始了String类的拷贝构造函数,写完之后,将结果给面试官看,...
      这次面试腾讯,面试官要我写个字符串拷贝函数,头一天晚上一晚上没睡好,脑袋昏昏沉沉的,之前看过C++中的字符串拷贝构造函数,先入为主,也没多想就开始了String类的拷贝构造函数,写完之后,将结果给面试官看,面试官说有几个问题,说怎么用这么长时间,然后我检查问题所在,第一个是字符串拷贝后,最后没有添加'\0',因为循环判断的条件是!='\0',所以很明显没有这个,将导致严重的安全问题,产生越界和内存非法访问问题。问题在于C语言中对字符串的读取是直到找到第一个'\0'才结束。虽然马上发现了,但是明显这是平时没有引起足够的重视引起的。第二个问题就是为什么要在里面重新开辟一块内存区来存储字符串,这个问题正是因为我把这个问题想成c++字符串拷贝构造函数,而C++的字符串实现中,字符串类大概定义如下: 
      class String
      {
      public:
      String(const char *str = NULL); // 普通构造函数
      String(const String &other); // 拷贝构造函数
      ~ String(void); // 析构函数
      String & operate =(const String &other); // 赋值函数
      private:
      char *m_data; // 用于保存字符串
      };
      而实现字符串拷贝构造函数基本就是如下:
      String::String(const String &other)
      {
      int len = strlen(other.m_data);
      m_data = new char[len+1];
      strcpy(m_data, other.m_data);
      }
      晕哦,现在回想起来,当时整个脑子估计处于昏迷状态。
      其实,就是简单的一个字符串拷贝,如果放在平时的话,简单的不能再简单。
      char *strcpy(char *strDest, const char *strSrc);
      {
      assert((strDest!=NULL) && (strSrc !=NULL));
      char *address = strDest;
      while( (*strDest++ = * strSrc++) != '\0' ) NULL ;
      return address ;
      }
      这里有三个需要注意的地方,第一个就是断言宏assert的使用,说真的,以前真还没具体的弄懂断言啥子个意思,assert这个单词用中文来理解真TM费力,倒是可以通过英语的解释来理解:
      同义词: affirm declare insist on pronounce state
      to declare or affirm solemnly and formally as true; affirm, verify
      assert to be true; insist
      这样来看,assert宏就是要求里面的条件都是真的,在运行过程中,如果assert 的参数为假,那么程序就会中止(一般地还会出现提示对话,说明在什么地方引发了assert)。
      第二个:while( (*strDest++ = * strSrc++) != '\0' ) NULL ; 这里++运算符优先级较*高,而且这个里面隐式包含了将'\0'赋值给字符串最后,因为采用的 指针后++,当strSrc为最后一个字符时,条件为真,后++后,strSrc指向'\0',先赋值,再比较,条件为假,退出循环,不好理解,自己也可以采用好理解的方式写。如下:
      while(*strSrc!='\0')
      {
      *strDest = * strSrc;
      strDest++;
      strSrc++;
      }
      *strDest ='\0';
      第三个问题:strcpy 能把strSrc 的内容复制到strDest,为什么还要char * 类型的返回值?这是为了实现链式表达式。 所谓链式表达式就是可以将strcpy函数作为其他函数的参数。
      例如 int length = strlen( strcpy( strDest, "hello world") );
      这次技术面试的第一个问题就弄成这样,虽然后面的问题答的可以,但是显然这样的开始给面试官不好的映象,如果给人家第一映象都不好,也就难怪面试官也会在心理上先入为主啦,其实还有我在hr面的时候,hr对我的穿着形式也提出了建议,这个可能会决定我的这次面试成功与否,总之细小的地方注定成败,这句话我现在是有深刻的体会啦。
    展开全文
  • 概述随着流行的缓冲区溢出攻击的增加,越来越多程序员开始...本文介绍另一种直观的,一致的,天生安全的字符串拷贝API 。当函数 strncpy()和 strncat()作为 strcpy()和 strcat()的安全版本来使用时,仍然存在一些安全隐

    概述

    随着流行的缓冲区溢出攻击的增加,越来越多程序员开始使用带有大小,即有长度限制的字符串函数,如strncpy()strncat() 。尽管这种趋势令人十分鼓舞,但通常的标准C 字符串函数并不是专为此而设计的。本文介绍另一种直观的,一致的,天生安全的字符串拷贝API

    当函数 strncpy()strncat()作为 strcpy()strcat()的安全版本来使用时,仍然存在一些安全隐患。

    1. 这两函数以不同的,非直观的方式来处理NUL 结束符和长度参数,即使有经验的程序员也会混淆。
    2. 发生字符串截断时,也不容易检查。
    3. strncpy()函数使用0 来填充剩余的目标字符串空间,以招致性能下降。在所有这些问题之中,由长度参数引起的混淆以及与NUL 结束符相关的问题最严重。在审核OpenBSD源代码树的潜在安全漏洞时,我们发现strncpy()strncat()猖獗误用的情况。尽管并非所有的误用都会导致可被利用的安全漏洞,但清楚地表明使用strncpy()strncat() 来实施安全的字符串操作这一准则已普遍受到误解。两个替代函数strlcpy()strlcat() 被提议通过提出一个字符串拷贝安全的API 来解决这些问题(参阅图1 函数原型)。这两函数保证产生包含NUL 的字符串,以长度即字符串按占用字节的数量作为入口参数,并且提供简便的方式来检查是否有字符串截断。两者均不会清零未使用的目标空间。

    引言

    1996 年年中,笔者和OpenBSD项目的其它成员一起担任审核OpenBSD 源代码树的工作,以寻找安全问题,并强调缓冲区溢出问题。缓冲区溢出问题最近在论坛上获得广泛的关注,并且也被广泛利用。我们发现大量的溢出是由于使用sprintf()strcpy()strcat() 而造成无长度界限的字符串拷贝,在循环里操纵字符串时没有显式检查字符串长度也是元凶之一。除此之外,我们也发现在很多场合下,程序员已使用strncpy()strncat() 进行安全的字符串操纵,但未能领会这些API 的精妙之处。

    因此在审核代码时,我们发现不仅有必要去检查是否使用不安全的函数,如strcpy()strcat() ,同时也要检查是是否有函数strncpy()strncat() 的不正确使用。检查是否正确使用并非总是显而易见,特别是使用“静态”变量或使用由malloc() 分配的缓冲区时,这些缓冲区总是预先就填满了NUL 结束符。我们得到一个结论:需要十分安全的函数来替代strncpy()strncat() ,从根本上简化程序员的工作,同时也使代码审核变得更容易。

    size_t strlcpy(char *dst, const char *src, size_t size);
    size_t strlcat(char *dst, const char *src, size_t size); 

    图 1: strlcpy()strlcat()ANSI C原型

    普遍的误解

    最普遍的误解莫过于认为函数 strncpy() 总是产生以NUL 结束的目标字符串。然而只有当源字符串的长度小于size 参数时,这一论断才为真。当拷贝任意长的用户输入到固定大小的缓冲区,问题就出现了。这种情况下,使用strncpy() 最安全的方法是先将目标字符串的大小减1 ,再传递给strncpysize 参数,然后手工给目标字符串加上NUL 结束符。这样可以保证目标字符串总是以NUL 结尾的。严格地说,如果字符串是“静态”变量或者由calloc() 分配的变量,完全没有必要手工给字符串加上NUL 结束符。因为这些字符串在分配时已经清零了。然而,依赖这一特性通常会给后来维护代码的人造成混乱。

    另一个误解认为把代码中的 strcpy()strcat() 换成strncpy()strncat() 所引起的性能下降微不足道。对于strncat() 来说,确实如此 。但对于 strncpy()来说则不是这样,因为它会把那些未用来存储字符串的字节清零。当目标字符串的大小远远大于源字符的长度时,这会导致为数不少 的性能下降。strncpy() 的行为因CPU 架构和它的实现而异,因此它所带来的性能下降也因它的行为而不同。

    使用 strncat()最普遍的错误是使用不正确的 size参数。确实要保证 strncat()使目标字符串包含 NULL结束符,参数 size决不能把 NULL字符的空间计算在内。最重要的是,参数 size不是目标字符串本身的大小,而是为字符串预留的空间的数量。由于参数 size 几乎总一个计算量,而非一个已知的常量,因此经常被错误地计算。

    strlcpy()和strlcat()是如何简化编程的?

    strlcpy()strlcat()函数提供一个一致的,绝无二义的 API ,帮助程序员编写更安全的防弹代码。首先,同时也是最重的,strlcpy()strlcat() 两者保证所有的目标字符串都以NUL 字符结尾,只要提供的size 参数为非零。其次,两个函数都把size 参数作为整个目标字符的大小。大多情况下,它的值很容易在编译时通过使用sizeof 运算符来计算。最后,strlcpy()strlcat() 均不给目标字符串清零未使用的字节(而是使用NUL 来表示字符串的结束)。

    strlcpy()strlcat()函数返回他们尝试创建的字符串的长度。对于 strlcpy()来说,就是源字符串的长度;而对 strlcat()来说,就是目标字符串的长度(串接前的长度)加上源字符串的长度。对于检查是否发生字符截断,程序员只需要验证回返值是否不小于size 参数。因此,就算发生截断,存储整个字符串所需的字节数现已知道,程序员可以分配一个更大的空间,接着重新拷贝字符串(如果需要的话)。返回值在语义上与snprintf() 的返回值类似。如果没有发生截断,程序员现在也获知了结果字符串的长度。由于通常的实践是使用strncpy()strncat()来构建字符串,然后使用strlen()来获得结果字符串的长度,因此(strlcpy() 和strlcat() )这一返回值语义非常有用。有了strlcpy()strlcat()后,就不再需要最后一步的strlen()来获得字符串的长度了。

    示例1a是有潜在缓冲区溢出的代码段(HOME 环境变量由用户所控制,可为任意长)。

    strcpy(path, homedir);
    strcat(path, "/");
    strcat(path, ".foorc");
    len = strlen(path);

    示例 1a: 使用strcpy()strcat() 的代码段

    示例 1b是同样功能的代码段,不过换成了安全地使用 strncpy()strncat()( 请注意我们不得已手工给目标字符串设置NUL 字符) 。

    strncpy(path, homedir,sizeof(path) - 1);
    path[sizeof(path) - 1] = '/ 0';
    strncat(path, "/",sizeof(path) - strlen(path) - 1);
    strncat(path, ".foorc",sizeof(path) - strlen(path) - 1);
    len = strlen(path);

    示例1b: 转换成使用strncpy()strncat()

    示例 1c是使用 strlcpy()/strlcat()API的平凡版本。它的优点是与示例 1a 一样简洁,但不需要利用新API 的返回值。

    strlcpy(path, homedir, sizeof(path));
    strlcat(path, "/", sizeof(path));
    strlcat(path, ".foorc", sizeof(path));
    len = strlen(path);

    示例 1c: 使用strlcpy()/strlcat() 的平凡版本

    由于示例 1c是如此的容易阅读和理解,故对它添加额外的检查显得格外简单。示例 1d 里检查返回值以确定是否有足够的空间来储存源字符串。如果没有足够空间,返回一个错误。虽然程序比以前有轻微的复杂,但更具鲁棒性,同时避免最后一步的strlen() 调用。

    len = strlcpy(path, homedir,sizeof(path));
    if (len >= sizeof(path))
        return (ENAMETOOLONG);
    
    len = strlcat(path, "/",sizeof(path));
    if (len >= sizeof(path))
        return (ENAMETOOLONG);
    
    len = strlcat(path, ".foorc",sizeof(path));
    if (len >= sizeof(path))
        return (ENAMETOOLONG);

    示列1d : 检测是否截断

    设计决策

    在考虑strlcpy()strlcat()应具有什么语义的时候,涌现出各种各样的想法。原先的想法是使strlcpy()strlcat()的语义和strncpy()strncat()的相同,唯一例外是:他们总是确保目标字符串以NUL 结尾。然而,回顾strncat()的普遍使用情况(和误用),我们深信strlcat()size参数应该是整个字符串空间的大小,而不仅是剩下来未分配的字符数。起初决定返回值为拷贝字符的数目???。很快我们决定返回值和snprintf()的具有相同的语义是这一个更好的选择,因为这样给予程序员最大的弹性去做截断检查和截断恢复。

    性能

    程序员现已开始避免使用strncpy()函数,原因是当目标缓冲区远远大于源字符串的长度时,该函数的性能欠佳。例如apache开发小组以调用内部函数来取代strncpy(),并公布了性能上的提升。同样地,ncurses 软件包最近删除了所有的strncpy()函数调用,结果tic工具的运行速度提高了四倍。我们谨希望,将来更多的程序员使用strlcpy()提供的接口,而非使用经定制的接口。

    为获得在最糟糕情况下,strncpy()strlcpy()差别的感性认识,我们运行一个测试程序,拷贝字符串“this is just a test”1000次到大小为1024字节的缓冲区。这对于strncpy()来说有点不公平,由于使用较短的字符串和较大的缓冲区,strncpy()必须为缓冲区大部分空间填充上NUL字符。然而在实践中,使用的缓冲区通常远远大于用户预期的输入。例如,路径名缓冲区的长度为MAXPATHLEN(1024字节) ,但大多数文件名远远小于这一长度。表1 中的平均运行时间是在使用25Mhz68040CPU的机器HP9000/425tOpenBSD 2.5操作系统下和使用166Mhzalpha CPU的机器DEC AXPPCI166OpenBSD 2.5操作系统下产生的结果。各种情况使用相同的C 函数版本,时间为time工具报告结果的“real time”部分。

    从表 1 可以看到, strncpy()的计时结果远差于strcpy()strlcpy()的结果。这可能不仅仅是因为填补NUL字符带来的开销,而且是因为CPU的数据缓存被长长的零串有效地刷新。

    展开全文
  • 在C标准库中提供了字符串拷贝函数strcpy,而微软则为为它提供了一个更安全的版本strcpy_s,其函数原型为errno_t __cdecl strcpy_s( char* _Destination, rsize_t _SizeInBytes, char const* _Source ); 分享下它...

    在C标准库中提供了字符串拷贝函数strcpy,而微软则为为它提供了一个更安全的版本strcpy_s,其函数原型为

    errno_t __cdecl strcpy_s(
        char*       _Destination,
        rsize_t     _SizeInBytes,
        char const* _Source
        );
    

    分享下它的实现和一些个人理解

    源码展示

    标准strcpy的实现

    // from gcc-4.8.5
    extern void abort (void);
    extern int inside_main;
    
    __attribute__ ((__noinline__))
    char *
    strcpy (char *d, const char *s)
    {
      char *r = d;
    #if defined __OPTIMIZE__ && !defined __OPTIMIZE_SIZE__
      if (inside_main)
        abort ();
    #endif
      while ((*d++ = *s++));
      return r;
    }
    
    // 简化一下
    char *strcpy (char *d, const char *s)
    {
      char *r = d;
      while ((*d++ = *s++));
      return r;
    }
    

    没什么好说的,懂得都懂(笑

    逐地址拷贝,当*d == '0'时,while循环退出结束拷贝,网上搜strcpy实现应该能找到很多详解,不赘述了

    微软strcpy_s的实现

    // from C:Program Files (x86)Microsoft Visual Studio 10.0VCcrtsrctcscpy_s.inl
    
    /***
    *tcscpy_s.inl - general implementation of _tcscpy_s
    *
    *       Copyright (c) Microsoft Corporation. All rights reserved.
    *
    *Purpose:
    *       This file contains the general algorithm for strcpy_s and its variants.
    *
    ****/
    
    _FUNC_PROLOGUE
    errno_t __cdecl _FUNC_NAME(_CHAR *_DEST, size_t _SIZE, const _CHAR *_SRC)
    {
        _CHAR *p;
        size_t available;
    
        /* validation section */
        _VALIDATE_STRING(_DEST, _SIZE);
        _VALIDATE_POINTER_RESET_STRING(_SRC, _DEST, _SIZE);
    
        p = _DEST;
        available = _SIZE;
        while ((*p++ = *_SRC++) != 0 && --available > 0)
        {
        }
    
        if (available == 0)
        {
            _RESET_STRING(_DEST, _SIZE);
            _RETURN_BUFFER_TOO_SMALL(_DEST, _SIZE);
        }
        _FILL_STRING(_DEST, _SIZE, _SIZE - available + 1);
        _RETURN_NO_ERROR;
    }
    

    首先明确一点,多出来的参数size_t _SIZE需要传入目的地址可用长度,即_DEST的可用长度

    实现中多了几个宏定义,我们先猜一下他们是干嘛的,然后带着疑问往下看。

    不感兴趣的同学也可以跳过这一章,直接看后面的分析结论

    详细分析过程

    注:以下为猜测内容,与实际可能有较大差异,正确解释请接着往后看每个宏定义的详细分析

    总体猜测

    • _VALIDATE_STRING:应该是验证字符串的合法性,是否为NULL,失败可能会直接return错误码;传入SIZE可能还会判断目的地址是否有这么长?但是这个咋判断呢,想不通
    • _VALIDATE_POINTER_RESET_STRING:看不懂,不过既然是VALIDATE(验证),估计还是做一些什么检查之类的吧,但是后面为啥又RESET呢?
    • _RESET_STRING:应该是将字符串重置,重置为NULL么?
    • _RETURN_BUFFER_TOO_SMALL:应该就是return了一个错误码吧,可能还包含错误信息啥的?
    • _FILL_STRING:应该是将字符串剩余部分填充为NULL?
    • _RETURN_NO_ERROR:应该就是return 0

    这样再看一遍代码下来,整体逻辑还是比较清晰的:

    1. 先两个_VALIDATE宏,验证目的字符串和源字符串的合法性
    2. 开始逐字符拷贝,如果正常拷到'0',或者available跑完了,就停止
    3. 如果2.中是available跑完了,说明SRC的长度超过了SIZE,即超过了目的字符串最大可用长度。拷贝失败了,重置DEST,整理错误信息,return错误码。
    4. 正常拷到'0',就把DEST剩余的后半部分[_SIZE - available + 1, _Size)全填充为某个比较安全的值。
    5. return 0 结束。

    下面逐个分析下这些宏,为了便于理解,我整理了一下,不要在意定义的先后顺序~

    _VALIDATE_STRING

    _VALIDATE_STRING(_DEST, _SIZE);
    
    // from internal_securecrt.h
    #define _VALIDATE_STRING(_String, _Size) 
        _VALIDATE_STRING_ERROR((_String), (_Size), EINVAL)
    

    得,套娃,我们接着看

    // from internal_securecrt.h
    #define _VALIDATE_STRING_ERROR(_String, _Size, _Ret) 
        _VALIDATE_RETURN((_String) != NULL && (_Size) > 0, EINVAL, (_Ret))
    
    // from errno.h
    #define EINVAL          22
    

    似乎好理解一点了,如果不满足(_String) != NULL && (_Size) > 0,可能会报错并返回EINVALEINVAL就是errno的错误码22,表示非法参数。原来Size只是判断是否大于0啊,那看来前面猜测的判断DEST长度是猜错了,确实没法实现这个

    但是_VALIDATE_RETURN的第二个和第三个参数都是EINVAL,又是干啥的?

    // from internal.h
    #ifndef _VALIDATE_RETURN
    #define _VALIDATE_RETURN( expr, errorcode, retexpr )                           
        {                                                                          
            int _Expr_val=!!(expr);                                                
            _ASSERT_EXPR( ( _Expr_val ), _CRT_WIDE(#expr) );                       
            if ( !( _Expr_val ) )                                                  
            {                                                                      
                errno = errorcode;                                                 
                _INVALID_PARAMETER(_CRT_WIDE(#expr) );                             
                return ( retexpr );                                                
            }                                                                      
        }
    #endif  /* _VALIDATE_RETURN */
    

    首先看明白了上面的疑问,第一个参数expr是判断条件,第二个参数errorcode是赋值给errno的(不了解errno的同学可以自行搜一下),第三个参数retexpr是用来return的

    然后我们接着来看套娃

    // from crtdbg.h
    #ifndef _DEBUG
        #ifndef _ASSERT_EXPR
            #define _ASSERT_EXPR(expr, msg) ((void)0)
        #endif  
    #else // ^^^ !_DEBUG ^^^ // vvv _DEBUG vvv //
    
        // !! is used to ensure that any overloaded operators used to evaluate expr
        // do not end up at &&.
        #ifndef _ASSERT_EXPR
            #define _ASSERT_EXPR(expr, msg) 
                (void)(                                                                                     
                    (!!(expr)) ||                                                                           
                    (1 != _CrtDbgReportW(_CRT_ASSERT, _CRT_WIDE(__FILE__), __LINE__, NULL, L"%ls", msg)) || 
                    (_CrtDbgBreak(), 0)                                                                     
                )
        #endif
    

    非Debug模式下,就什么也不做,直接转((void)0);Debug模式下,_CrtDbgReportW是弹出对话框报错,_CrtDbgBreak是触发调试断点,这个扯远了,不再深度展开,有兴趣的同学再另外看下吧~

    // from internal.h
    #define _INVALID_PARAMETER(expr) _CALL_INVALID_PARAMETER(expr)
    
    // from internal.h
    #define _CALL_INVALID_PARAMETER(expr) _invalid_parameter(expr, __FUNCTIONW__, __FILEW__, __LINE__, 0)
    

    _INVALID_PARAMETER说实话我真没太看明白在干嘛,继续追踪后来会到invarg.c中,套娃套的太多了,看不过来了。。获取了__FUNCTION____LINE__等,估计是记录错误,可能在VS的调试器等中有体现,有兴趣的同学也自行也就看下吧

    小结(-VALIDATE-STRING)

    总结一下,就是检查(_DEST != NULL && _Size > 0),不满足的话,赋值errno,并直接返回错误码,(_DEBUG模式下,还会弹窗提示,并触发调试断点)。简单实现如下

    // Same like _VALIDATE_STRING(_DEST, _SIZE);
    if (_DEST == NULL || _Size <= 0)
    {
        errno = EINVAL;
        return EINVAL;
    }
    

    看完了第一个宏,想必对这些宏的套路也有一些了解,后面的就不每个这么详细展开了

    _VALIDATE_POINTER_RESET_STRING

    _VALIDATE_POINTER_RESET_STRING(_SRC, _DEST, _SIZE);
    
    // from internal_securecrt.h
    #define _VALIDATE_POINTER_RESET_STRING(_Pointer, _String, _Size) 
        _VALIDATE_POINTER_RESET_STRING_ERROR((_Pointer), (_String), (_Size), EINVAL)
    
    // from internal_securecrt.h
    #define _VALIDATE_POINTER_RESET_STRING_ERROR(_Pointer, _String, _Size, _Ret) 
        if ((_Pointer) == NULL) 
        { 
            _RESET_STRING((_String), (_Size)); 
            _VALIDATE_POINTER_ERROR_RETURN((_Pointer), EINVAL, (_Ret)) 
        }
    

    如果_SRCNULL,才进行_RESET_STRING_VALIDATE_POINTER_ERROR_RETURN的操作,就是如果源字符串NULL,直接把目的字符串重置,然后返回验证一个什么值并返回

    // from internal_securecrt.h
    #define _RESET_STRING(_String, _Size) 
        *(_String) = 0; 
        _FILL_STRING((_String), (_Size), 1);
    
    // from internal_securecrt.h
    /* string resetting */
    #define _FILL_STRING _SECURECRT__FILL_STRING
    
    // from internal.h
    #if _SECURECRT_FILL_BUFFER
    #define _SECURECRT__FILL_STRING(_String, _Size, _Offset)                            
        if ((_Size) != ((size_t)-1) && (_Size) != INT_MAX &&                            
            ((size_t)(_Offset)) < (_Size))                                              
        {                                                                               
            memset((_String) + (_Offset),                                               
                _SECURECRT_FILL_BUFFER_PATTERN,                                         
                (_SECURECRT_FILL_BUFFER_THRESHOLD < ((size_t)((_Size) - (_Offset))) ?   
                    _SECURECRT_FILL_BUFFER_THRESHOLD :                                  
                    ((_Size) - (_Offset))) * sizeof(*(_String)));                       
        }
    #else  /* _SECURECRT_FILL_BUFFER */
    #define _SECURECRT__FILL_STRING(_String, _Size, _Offset)
    #endif  /* _SECURECRT_FILL_BUFFER */
    
    // from internal.h
    #ifdef _DEBUG
    #define _SECURECRT_FILL_BUFFER 1
    #else  /* _DEBUG */
    #define _SECURECRT_FILL_BUFFER 0
    #endif  /* _DEBUG */
    
    // from corecrt.h, 0xFE = 254
    #define _SECURECRT_FILL_BUFFER_PATTERN 0xFE
    
    // from internal.h
    #ifdef _DEBUG
    #define _SECURECRT_FILL_BUFFER_THRESHOLD __crtDebugFillThreshold
    #else  /* _DEBUG */
    #define _SECURECRT_FILL_BUFFER_THRESHOLD ((size_t)0)
    #endif  /* _DEBUG */
    
    // from dbgheap.c
    extern "C" size_t __crtDebugFillThreshold = SIZE_MAX;
    

    这里比较巧妙,重置字符串,一般情况下仅将_DSET首个char的值赋值 *(_DEST) = 0;;DEBUG模式下才_FILL_STRING,其中还用_SECURECRT_FILL_BUFFER_THRESHOLD长度判断,还是跟着DEBUG走的,用memset将后面剩下的char赋值为254。后面在_FILL_STRING章节详细论述一下这个

    // from internal_securecrt.h
    #define _VALIDATE_POINTER_ERROR_RETURN(_Pointer, _ErrorCode, _Ret) 
        _VALIDATE_RETURN((_Pointer) != NULL, (_ErrorCode), (_Ret))
    

    小结(-VALIDATE-POINTER-RESET-STRING)

    上个宏检查DEST,这个宏检查_SRC != NULL,不满足则将DEST首个字符的值赋0,DEBUG模式下会有一套更安全但是更耗性能的DEST重置方式,放在后面_FILL_STRING的时候一起写

    // Same like _VALIDATE_POINTER_RESET_STRING(_SRC, _DEST, _SIZE);
    if (_SRC == NULL) {
        *_DEST = 0;
    #ifdef _DEBUG
        _FILL_STRING(_DEST, _SIZE, 1);
    #endif
        errno = EINVAL;
        return EINVAL;
    }
    

    _RESET_STRING

    _RESET_STRING(_DEST, _SIZE);
    
    // from internal_securecrt.h
    #define _RESET_STRING(_String, _Size) 
        *(_String) = 0; 
        _FILL_STRING((_String), (_Size), 1);
    

    上面已经套娃过这个宏了,不再赘述

    小结(-RESET-STRING)

    同样_DEBUG的时候,更安全的重置方式,放在后面_FILL_STRING的时候一起写

    // Same like _RESET_STRING(_DEST, _SIZE);
    *_DEST = 0;
    #ifdef _DEBUG
        _FILL_STRING(_DEST, _SIZE, 1);
    #endif
    

    _RETURN_BUFFER_TOO_SMALL

    _RETURN_BUFFER_TOO_SMALL(_DEST, _SIZE);
    
    // from internal_securecrt.h
    #define _RETURN_BUFFER_TOO_SMALL(_String, _Size) 
        _RETURN_BUFFER_TOO_SMALL_ERROR((_String), (_Size), ERANGE)
    
    // from internal_securecrt.h
    #define _RETURN_BUFFER_TOO_SMALL_ERROR(_String, _Size, _Ret) 
        _VALIDATE_RETURN((L"Buffer is too small" && 0), ERANGE, _Ret)
    
    // from errno.h
    #define ERANGE          34
    

    小结(-RETURN-BUFFER-TOO-SMALL)

    // Same like _RETURN_BUFFER_TOO_SMALL(_DEST, _SIZE);
    errno = ERANGE;
    return ERANGE;
    

    _FILL_STRING

    终于讲到前面反复提及的_FILL_STRING了,其实也并没有很复杂,先看下定义

    // from internal_securecrt.h
    /* string resetting */
    #define _FILL_STRING _SECURECRT__FILL_STRING
    
    // from internal.h
    #if _SECURECRT_FILL_BUFFER
    #define _SECURECRT__FILL_STRING(_String, _Size, _Offset)                            
        if ((_Size) != ((size_t)-1) && (_Size) != INT_MAX &&                            
            ((size_t)(_Offset)) < (_Size))                                              
        {                                                                               
            memset((_String) + (_Offset),                                               
                _SECURECRT_FILL_BUFFER_PATTERN,                                         
                (_SECURECRT_FILL_BUFFER_THRESHOLD < ((size_t)((_Size) - (_Offset))) ?   
                    _SECURECRT_FILL_BUFFER_THRESHOLD :                                  
                    ((_Size) - (_Offset))) * sizeof(*(_String)));                       
        }
    #else  /* _SECURECRT_FILL_BUFFER */
    #define _SECURECRT__FILL_STRING(_String, _Size, _Offset)
    #endif  /* _SECURECRT_FILL_BUFFER */
    
    // from internal.h
    #ifdef _DEBUG
    #define _SECURECRT_FILL_BUFFER 1
    #else  /* _DEBUG */
    #define _SECURECRT_FILL_BUFFER 0
    #endif  /* _DEBUG */
    
    // from corecrt.h, 0xFE = 254
    #define _SECURECRT_FILL_BUFFER_PATTERN 0xFE
    
    // from internal.h
    #ifdef _DEBUG
    #define _SECURECRT_FILL_BUFFER_THRESHOLD __crtDebugFillThreshold
    #else  /* _DEBUG */
    #define _SECURECRT_FILL_BUFFER_THRESHOLD ((size_t)0)
    #endif  /* _DEBUG */
    
    // from dbgheap.c
    extern "C" size_t __crtDebugFillThreshold = SIZE_MAX;
    

    _FILL_STRING整个宏,仅在_DEBUG定义时work,否则什么也不做;

    _SECURECRT__FILL_STRING(_String, _Size, _Offset) 三个参数很好理解,目的字符串、目的字符串长度、填充的起始偏移量

    if ((_Size) != ((size_t)-1) && (_Size) != INT_MAX && ((size_t)(_Offset)) < (_Size)) 就是检查_Size合法性,并且要求_Offset小于_Size

    然后memset(_String) + (_Offset)地址开始,填充0xFE。非DEBUG模式下填充0个字节,还是相当于什么也不做,多一重判断;DEBUG模式下填充(_Size - _Offset) * sizeof(char)

    小结(-FILL-STRING)

    // Same like _FILL_STRING(_DEST, _SIZE, _OFFSET);
    #ifdef _DEBUG
        if (_OFFSET < _Size)
        {
            memset(_DEST + _OFFSET, 0xFE, (_Size - _Offset) * sizeof(char));
        }
    #else
    // Do nothing;
    #endif
    

    _RETURN_NO_ERROR

    // from internal_securecrt.h
    /* returns without calling _invalid_parameter */
    #define _RETURN_NO_ERROR 
        return 0
    

    终于有一个不套娃的了XD

    小结(-RETURN-NO-ERROR)

    // Same like _RETURN_NO_ERROR;
    return 0;
    

    分析结论

    再粘一遍源码,方便对照着看

    _FUNC_PROLOGUE
    errno_t __cdecl _FUNC_NAME(_CHAR *_DEST, size_t _SIZE, const _CHAR *_SRC)
    {
        _CHAR *p;
        size_t available;
    
        /* validation section */
        _VALIDATE_STRING(_DEST, _SIZE);
        _VALIDATE_POINTER_RESET_STRING(_SRC, _DEST, _SIZE);
    
        p = _DEST;
        available = _SIZE;
        while ((*p++ = *_SRC++) != 0 && --available > 0)
        {
        }
    
        if (available == 0)
        {
            _RESET_STRING(_DEST, _SIZE);
            _RETURN_BUFFER_TOO_SMALL(_DEST, _SIZE);
        }
        _FILL_STRING(_DEST, _SIZE, _SIZE - available + 1);
        _RETURN_NO_ERROR;
    }
    
    1. _VALIDATE_STRING(_DEST, _SIZE); 检验_DEST != NULL && _Size > 0是否满足;若为假,则errno = EINVAL,并直接return EINVAL;如果是在Debug模式下(_DEBUG宏被定义过)还会弹出提示窗口、触发调试断点、记录下错误发生位置等
    2. _VALIDATE_POINTER_RESET_STRING(_SRC, _DEST, _SIZE); 判断_SRC == NULL是否满足;若为真,则Reset _DESTerrno = EINVAL,并直接return EINVAL,Debug模式下也同样弹窗、断点、错误等
    3. 算法逻辑:与分析中的没有区别,逐字符拷贝,如果正常拷到'0',或者available跑完了,就停止
    4. 如果available == 0,说明_SRC的长度超过了_SIZE,即超过了目的字符串最大可用长度,拷贝失败。
    5. _RESET_STRING(_DEST, _SIZE); 重置字符串,*_DEST = 0;,且_FILL_STRING(_DEST, _SIZE, 1);。即将首个字符赋值为'0',后面的字符填充安全字符0xFE。需要说明的是,_FILL_STRING也仅在Debug模式下才进行,否则什么也不处理
    6. _RETURN_BUFFER_TOO_SMALL(_DEST, _SIZE); errno = ERANGE,并直接return ERANGE,Debug模式下也同样弹窗、断点、错误等
    7. _FILL_STRING(_DEST, _SIZE, _SIZE - available + 1);就是调用memset,将_DEST_SRC多出来的部分([_SIZE - available + 1, _Size))全部填充为安全字符0xFE。同样,_FILL_STRING也仅在Debug模式下才进行,否则什么也不处理

    可能有点绕,接着看下一章节简化实现应该就清晰了~

    简化实现

    看完了源码,我们来写个简单点的strcpy_s吧

    void fill_string(char * string, size_t size, size_t offset);
    
    errno_t strcpy_s(char *_DEST, size_t _SIZE, const char *_SRC)
    {
        char *p;
        size_t available;
    
        if (!(_DEST != NULL && _Size > 0))
        {
            errno = EINVAL;
            return EINVAL;
        }
        if (_SRC == NULL) {
            *_DEST = 0;
            fill_string(_DEST, _SIZE, 1);
            errno = EINVAL;
            return EINVAL;
        }
    
        p = _DEST;
        available = _SIZE;
        while ((*p++ = *_SRC++) != 0 && --available > 0)
        {
        }
    
        if (available == 0)
        {
            *_DEST = 0;
            fill_string(_DEST, _SIZE, 1);
            errno = ERANGE;
            return ERANGE;
        }
        fill_string(_DEST, _SIZE, _SIZE - available + 1);
        return 0;
    }
    
    inline void fill_string(char * string, size_t size, size_t offset)
    {
    #ifdef _DEBUG
        if (offset < size)
        {
            memset(string + offset, 0xFE, (size - offset) * sizeof(char));
        }
    #else
        // do nothing
        ;
    #endif
    }
    

    扩展延伸

    既然讲完了strcpy_s,那其他的字符串操作函数的_safe版本呢?下面再看下strcat_sstrset_s。也不多啰嗦了,直接粘出没见过的宏的实现,然后我们在写个简化实现看下~

    strcat_s

    /***
    *tcscat_s.inl - general implementation of _tcscpy_s
    *
    *       Copyright (c) Microsoft Corporation. All rights reserved.
    *
    *Purpose:
    *       This file contains the general algorithm for strcat_s and its variants.
    *
    ****/
    
    _FUNC_PROLOGUE
    errno_t __cdecl _FUNC_NAME(_CHAR *_DEST, size_t _SIZE, const _CHAR *_SRC)
    {
        _CHAR *p;
        size_t available;
    
        /* validation section */
        _VALIDATE_STRING(_DEST, _SIZE);
        _VALIDATE_POINTER_RESET_STRING(_SRC, _DEST, _SIZE);
    
        p = _DEST;
        available = _SIZE;
        while (available > 0 && *p != 0)
        {
            p++;
            available--;
        }
    
        if (available == 0)
        {
            _RESET_STRING(_DEST, _SIZE);
            _RETURN_DEST_NOT_NULL_TERMINATED(_DEST, _SIZE);
        }
    
        while ((*p++ = *_SRC++) != 0 && --available > 0)
        {
        }
    
        if (available == 0)
        {
            _RESET_STRING(_DEST, _SIZE);
            _RETURN_BUFFER_TOO_SMALL(_DEST, _SIZE);
        }
        _FILL_STRING(_DEST, _SIZE, _SIZE - available + 1);
        _RETURN_NO_ERROR;
    }
    

    没见过的宏:

    // from internal_securecrt.h
    #define _RETURN_DEST_NOT_NULL_TERMINATED(_String, _Size) 
        _VALIDATE_RETURN((L"String is not null terminated" && 0), EINVAL, EINVAL)
    

    简化实现:

    void fill_string(char * string, size_t size, size_t offset);
    
    errno_t strcat_s(char *_DEST, size_t _SIZE, const char *_SRC)
    {
        char *p;
        size_t available;
    
        if (!(_DEST != NULL && _Size > 0))
        {
            errno = EINVAL;
            return EINVAL;
        }
        if (_SRC == NULL) {
            *_DEST = 0;
            fill_string(_DEST, _SIZE, 1);
            errno = EINVAL;
            return EINVAL;
        }
    
        p = _DEST;
        available = _SIZE;
        while (available > 0 && *p != 0)
        {
            p++;
            available--;
        }
    
        if (available == 0)
        {
            *_DEST = 0;
            fill_string(_DEST, _SIZE, 1);
            errno = EINVAL;
            return EINVAL;
        }
    
        while ((*p++ = *_SRC++) != 0 && --available > 0)
        {
        }
    
        if (available == 0)
        {
            *_DEST = 0;
            fill_string(_DEST, _SIZE, 1);
            errno = ERANGE;
            return ERANGE;
        }
        fill_string(_DEST, _SIZE, _SIZE - available + 1);
        return 0;
    }
    
    inline void fill_string(char * string, size_t size, size_t offset)
    {
    #ifdef _DEBUG
        if (offset < size)
        {
            memset(string + offset, 0xFE, (size - offset) * sizeof(char));
        }
    #else
        // do nothing
        ;
    #endif
    

    strset_s

    /***
    *tcsset_s.inl - general implementation of _tcsset_s
    *
    *       Copyright (c) Microsoft Corporation. All rights reserved.
    *
    *Purpose:
    *       This file contains the general algorithm for _strset_s and its variants.
    *
    ****/
    
    _FUNC_PROLOGUE
    errno_t __cdecl _FUNC_NAME(_CHAR *_DEST, size_t _SIZE, _CHAR_INT _Value)
    {
        _CHAR *p;
        size_t available;
    
        /* validation section */
        _VALIDATE_STRING(_DEST, _SIZE);
    
        p = _DEST;
        available = _SIZE;
        while (*p != 0 && --available > 0)
        {
            *p++ = (_CHAR)_Value;
        }
    
        if (available == 0)
        {
            _RESET_STRING(_DEST, _SIZE);
            _RETURN_DEST_NOT_NULL_TERMINATED(_DEST, _SIZE);
        }
        _FILL_STRING(_DEST, _SIZE, _SIZE - available + 1);
        _RETURN_NO_ERROR;
    }
    

    简化实现:

    void fill_string(char * string, size_t size, size_t offset);
    
    errno_t strset_s(char *_DEST, size_t _SIZE, int _Value)
    {
        char *p;
        size_t available;
    
        if (!(_DEST != NULL && _Size > 0))
        {
            errno = EINVAL;
            return EINVAL;
        }
    
        p = _DEST;
        available = _SIZE;
        while (*p != 0 && --available > 0)
        {
            *p++ = (char)_Value;
        }
    
        if (available == 0)
        {
            *_DEST = 0;
            fill_string(_DEST, _SIZE, 1);
            errno = EINVAL;
            return EINVAL;
        }
        fill_string(_DEST, _SIZE, _SIZE - available + 1);
        return 0;
    }
    
    inline void fill_string(char * string, size_t size, size_t offset)
    {
    #ifdef _DEBUG
        if (offset < size)
        {
            memset(string + offset, 0xFE, (size - offset) * sizeof(char));
        }
    #else
        // do nothing
        ;
    #endif
    }
    

    进一步扩展

    聊完了普通版本的_Safe版本string函数,再进一步扩展下所有的string函数safe版本

    肝力有限,先把微软的实现粘出来,有空再更新吧

    strtok_s

    /***
    *tcstok_s.inl - general implementation of _tcstok_s
    *
    *       Copyright (c) Microsoft Corporation. All rights reserved.
    *
    *Purpose:
    *       This file contains the general algorithm for strtok_s and its variants.
    *
    ****/
    
    _FUNC_PROLOGUE
    _CHAR * __cdecl _FUNC_NAME(_CHAR *_String, const _CHAR *_Control, _CHAR **_Context)
    {
        _CHAR *token;
        const _CHAR *ctl;
    
        /* validation section */
        _VALIDATE_POINTER_ERROR_RETURN(_Context, EINVAL, NULL);
        _VALIDATE_POINTER_ERROR_RETURN(_Control, EINVAL, NULL);
        _VALIDATE_CONDITION_ERROR_RETURN(_String != NULL || *_Context != NULL, EINVAL, NULL);
    
        /* If string==NULL, continue with previous string */
        if (!_String)
        {
            _String = *_Context;
        }
    
        /* Find beginning of token (skip over leading delimiters). Note that
        * there is no token iff this loop sets string to point to the terminal null. */
        for ( ; *_String != 0 ; _String++)
        {
            for (ctl = _Control; *ctl != 0 && *ctl != *_String; ctl++)
                ;
            if (*ctl == 0)
            {
                break;
            }
        }
    
        token = _String;
    
        /* Find the end of the token. If it is not the end of the string,
        * put a null there. */
        for ( ; *_String != 0 ; _String++)
        {
            for (ctl = _Control; *ctl != 0 && *ctl != *_String; ctl++)
                ;
            if (*ctl != 0)
            {
                *_String++ = 0;
                break;
            }
        }
    
        /* Update the context */
        *_Context = _String;
    
        /* Determine if a token has been found. */
        if (token == _String)
        {
            return NULL;
        }
        else
        {
            return token;
        }
    }
    

    strncpy_s

    /***
    *tcsncpy_s.inl - general implementation of _tcsncpy_s
    *
    *       Copyright (c) Microsoft Corporation. All rights reserved.
    *
    *Purpose:
    *       This file contains the general algorithm for strncpy_s and its variants.
    *
    ****/
    
    _FUNC_PROLOGUE
    errno_t __cdecl _FUNC_NAME(_CHAR *_DEST, size_t _SIZE, const _CHAR *_SRC, size_t _COUNT)
    {
        _CHAR *p;
        size_t available;
    
        if (_COUNT == 0 && _DEST == NULL && _SIZE == 0)
        {
            /* this case is allowed; nothing to do */
            _RETURN_NO_ERROR;
        }
    
        /* validation section */
        _VALIDATE_STRING(_DEST, _SIZE);
        if (_COUNT == 0)
        {
            /* notice that the source string pointer can be NULL in this case */
            _RESET_STRING(_DEST, _SIZE);
            _RETURN_NO_ERROR;
        }
        _VALIDATE_POINTER_RESET_STRING(_SRC, _DEST, _SIZE);
    
        p = _DEST;
        available = _SIZE;
        if (_COUNT == _TRUNCATE)
        {
            while ((*p++ = *_SRC++) != 0 && --available > 0)
            {
            }
        }
        else
        {
            _ASSERT_EXPR((!_CrtGetCheckCount() || _COUNT < _SIZE), L"Buffer is too small");
    
            while ((*p++ = *_SRC++) != 0 && --available > 0 && --_COUNT > 0)
            {
            }
            if (_COUNT == 0)
            {
                *p = 0;
            }
        }
    
        if (available == 0)
        {
            if (_COUNT == _TRUNCATE)
            {
                _DEST[_SIZE - 1] = 0;
                _RETURN_TRUNCATE;
            }
            _RESET_STRING(_DEST, _SIZE);
            _RETURN_BUFFER_TOO_SMALL(_DEST, _SIZE);
        }
        _FILL_STRING(_DEST, _SIZE, _SIZE - available + 1);
        _RETURN_NO_ERROR;
    }
    

    strncat_s

    /***
    *tcsncat_s.inl - general implementation of _tcscpy_s
    *
    *       Copyright (c) Microsoft Corporation. All rights reserved.
    *
    *Purpose:
    *       This file contains the general algorithm for strncat_s and its variants.
    *
    ****/
    
    _FUNC_PROLOGUE
    errno_t __cdecl _FUNC_NAME(_CHAR *_DEST, size_t _SIZE, const _CHAR *_SRC, size_t _COUNT)
    {
        _CHAR *p;
        size_t available;
    
        if (_COUNT == 0 && _DEST == NULL && _SIZE == 0)
        {
            /* this case is allowed; nothing to do */
            _RETURN_NO_ERROR;
        }
    
        /* validation section */
        _VALIDATE_STRING(_DEST, _SIZE);
        if (_COUNT != 0)
        {
            _VALIDATE_POINTER_RESET_STRING(_SRC, _DEST, _SIZE);
        }
    
        p = _DEST;
        available = _SIZE;
        while (available > 0 && *p != 0)
        {
            p++;
            available--;
        }
    
        if (available == 0)
        {
            _RESET_STRING(_DEST, _SIZE);
            _RETURN_DEST_NOT_NULL_TERMINATED(_DEST, _SIZE);
        }
    
        if (_COUNT == _TRUNCATE)
        {
            while ((*p++ = *_SRC++) != 0 && --available > 0)
            {
            }
        }
        else
        {
            _ASSERT_EXPR((!_CrtGetCheckCount() || _COUNT < available), L"Buffer is too small");
    
            while (_COUNT > 0 && (*p++ = *_SRC++) != 0 && --available > 0)
            {
                _COUNT--;
            }
            if (_COUNT == 0)
            {
                *p = 0;
            }
        }
    
        if (available == 0)
        {
            if (_COUNT == _TRUNCATE)
            {
                _DEST[_SIZE - 1] = 0;
                _RETURN_TRUNCATE;
            }
            _RESET_STRING(_DEST, _SIZE);
            _RETURN_BUFFER_TOO_SMALL(_DEST, _SIZE);
        }
        _FILL_STRING(_DEST, _SIZE, _SIZE - available + 1);
        _RETURN_NO_ERROR;
    }
    

    strnset_s

    /***
    *tcsnset_s.inl - general implementation of _tcsnset_s
    *
    *       Copyright (c) Microsoft Corporation. All rights reserved.
    *
    *Purpose:
    *       This file contains the general algorithm for _strnset_s and its variants.
    *
    ****/
    
    _FUNC_PROLOGUE
    errno_t __cdecl _FUNC_NAME(_CHAR *_DEST, size_t _SIZE, _CHAR_INT _Value, size_t _COUNT)
    {
        _CHAR *p;
        size_t available;
    
        /* validation section */
        if (_COUNT == 0 && _DEST == NULL && _SIZE == 0)
        {
            /* this case is allowed; nothing to do */
            _RETURN_NO_ERROR;
        }
        _VALIDATE_STRING(_DEST, _SIZE);
    
        _ASSERT_EXPR((!_CrtGetCheckCount() || _COUNT < _SIZE), L"Buffer is too small");
    
        p = _DEST;
        available = _SIZE;
        while (*p != 0 && _COUNT > 0 && --available > 0)
        {
            *p++ = (_CHAR)_Value;
            --_COUNT;
        }
    
        if (_COUNT == 0)
        {
            /* ensure the string is null-terminated */
            while (*p != 0 && --available > 0)
            {
                ++p;
            }
        }
    
        if (available == 0)
        {
            _RESET_STRING(_DEST, _SIZE);
            _RETURN_DEST_NOT_NULL_TERMINATED(_DEST, _SIZE);
        }
        _FILL_STRING(_DEST, _SIZE, _SIZE - available + 1);
        _RETURN_NO_ERROR;
    }
    

    参考资料

    • C++string函数之strcpy_s 注:该文章中对几个宏的解释存在错误,请甄别阅读
    • strcpy_s的函数实现是啥?

    本文首发于我的个人博客,欢迎大家来逛逛~~~

    展开全文
  • title: 安全的字符串拷贝strcpy_s的实现与理解 date: 2020-10-21 15:29:00 tags: - C/C++ - STL - 算法 在C标准库中提供了字符串拷贝函数strcpy,而微软则为为它提供了一个更安全的版本strcpy_s,其函数原型为 errno...
  • 安全函数也就是系统函数,比如字符串拷贝函数strcpy返回值是一个指向最终目标字符串 dest 指针(非安全函数不知道返回什么时候就会返回这个);而安全函数strcpy_s返回一个整形错误码errno_t,表示函数...
  • 一致的、安全的字符串拷贝和串接函数 Todd C. Miller University of Colorado, Boulder Theo de Raadt OpenBSD project   概述   随着流行的缓冲区溢出攻击的增加,越来越多程序员开始使用带有...
  • #define _CRT_SECURE_NO_WARNINGS//强行去掉安全检查#include&lt;stdio.h&gt;#include&lt;stdlib.h&gt;#include&lt;Windows.h&gt;//完美函数//1:传地址,传递//2:被调用函数要有返回值 ...
  • 一致的、安全的字符串拷贝和串接函数 Todd C. Miller University of Colorado, Boulder Theo de Raadt OpenBSD project   概述   随着流行的缓冲区溢出攻击的增加...
  • 引用此文只为自己知识收集所用,如需引用请看下面作者和译者声明 英文原文: http://www.gratisoft.us/todd/papers/strlcpy.html 英文作者: Todd C. Miller, Theo de Raadt 译者:林海枫 译本地址:...
  • strlcpy是一个类似strcpy的字符串拷贝函数,它的目的是保证拷贝过程中缓冲区不会溢出,并且最后缓冲区内是一个有效的C字符串。它的定义如下: size_t strlcpy(char * dest, const char * src, size_t size); 参数: ...
  • 我们已经学习了使用char*来存储一个...把源字符串拷贝到目标字符串。作为程序员,我们要负责保证目标字符串存储空间能包容源字符串。strcmp:比较字符串strcmp(字符串1, 字符串2);按当前编码规则比较两个字符串...
  • 关于安全字符串操作函数

    千次阅读 2011-02-25 00:11:00
    当我们在VC7.0及以上版本中使用非安全的字符串操作函数时,默认情况会提示类似如下的警告: warning C4996: 'wcsncpy': This function or variable may be unsafe. Consider using wcsncpy_s instead. To disable ...
  • C里操作字符串很高效,但也很麻烦。...最常用的函数,但是却不安全,原因在于,一是要destination有足够空间,二是要保证source和destination指向空间没有overlap。 2. int sprintf ( char * str, const
  • 我们已经学习了使用char*来存储一个...把源字符串拷贝到目标字符串。作为程序员,我们要负责保证目标字符串存储空间能包容源字符串。strcmp:比较字符串strcmp(字符串1, 字符串2);按当前编码规则比较两个字符串...
  • 在VC中使用安全字符串操作函数

    千次阅读 2012-04-10 18:20:55
    strcpy/wcscpy以及其他大多数旧式的字符串处理函数来说,最大的问题是,它们只根据字符串末尾的\0来判断文本是否结束,而指定缓冲区的大小对它们来说是透明的,这就使得它们自己也不知道自己是否做了不该做的事情。...
  • 字符数组拷贝函数(一)

    万次阅读 2012-12-04 15:53:20
    常用的字符串拷贝函数为strcpy,strncpy,在高版本的中支持安全的拷贝函数:strcpy_s,strncpy_s。 2、函数原型 strcpy函数原型(一): char *strcpy(  char *strDestination,//Destination string.  const ...
  • 安全的strlcpy和strlcat字符串操作函数

    千次阅读 2013-11-12 10:19:15
    学过C语言的肯定都知道strcpy和strcat,但是这两个函数有个致命的缺陷,它们不检查dst是否有足够的空间,如果src足够长必然会导致缓冲区溢出,于是有...size_t num参数表示需要拷贝的字符个数,在num小于等于src的情况

空空如也

空空如也

1 2 3 4 5 ... 17
收藏数 336
精华内容 134
关键字:

安全的字符串拷贝函数