printf和println_void与int的区别

printf和println_void与int的区别sprintf、vsprintf、sprintf_s、vsprintf_s、_snprintf、_vsnprintf、snprintf、vsnprintf 函数辨析看了题目中的几个函数名是不是有点头晕?为了防止以后总在这样的细节里纠缠不

sprintf、vsprintf、sprintf_s、vsprintf_s、_snprintf、_vsnprintf、snprintf、vsnprintf 函数辨析   看了题目中的几个函数名是不是有点头晕?为了防止以后总在这样的细节里纠缠不清,今天我们就来好好地辨析一下这几个函数的异同。   实验环境:   Windows下使用VS2017   Linux下使用gcc4.9.4       为了验证函数的安全性我们设计了如下结构   const int len = 4; #pragma pack(push) #pragma pack(1) struct Data { char buf[len]; char guard; Data() { for (int i = 0; i < len; ++i) { buf[i] = ‘*’; } guard = 0xF; } void Display() { std::cout << “sizeof(Data) = ” << sizeof(Data) << std::endl; std::cout << “buf = ” << buf << std::endl; std::cout << “guard = ” << (unsigned int)guard << std::endl; if (guard != 0xF) { std::cout << “memory has been broken.” << std::endl; } std::cout << “—————” << std::endl; } }; #pragma pack(pop)   当我们把数据写到Data.buf字段中去的时候,如果发生了内存越界的情况,Data.gurad字段的内存会被修改。我们以此来推断函数的安全性。   一、sprintf(Linux/Windows)   Linux下的函数原型:int sprintf(char *str, const char *format, …);   测试代码:   int main() { Data data; data.Display(); int ret = sprintf(data.buf, “%d”, 12); std::cout << “ret = ” << ret << std::endl; data.Display(); std::cin.get(); return 0; }   在VS2017环境中,这个函数被标记为不安全的,如果使用了,编译器会报警告,如果非要使用,必须在编译的时候增加宏定义:_CRT_SECURE_NO_WARNINGS,告诉编译器忽略安全警告。在Linux下此函数可以正常使用。而且这个函数在Windows下和Linux下行为也是一样的。具体如下:   1.当源数据的长度【小于】len,sprintf把数据完整的写到目标内存,并保证尾部以0结尾,返回写入的字节数。此时该函数的行为是安全的。   例如:    sprintf(data.buf, “%d”, 12);    输出:   sizeof(Data) = 5 buf = 烫烫烫 guard = 15 ————— ret = 2 sizeof(Data) = 5 buf = 12 guard = 15 —————       2.当源数据的长度【等于】len,sprintf把数据完整的写到目标内存,并在目标内存的尾部多写入一个0,返回写入的字节数。此时该函数已经发生拷贝越界的情况了。所以,当用户以为分配的内存刚刚好满足拷贝需求的时候,其实已经发生了潜在的风险。   例如:    sprintf(data.buf, “%d”, 1234);    输出:   sizeof(Data) = 5 buf = 烫烫烫 guard = 15 ————— ret = 4 sizeof(Data) = 5 buf = 1234 guard = 0 memory has been broken. —————   3.当源数据的长度【大于】len,sprintf把数据完整的写到目标内存,返回写入的字节数,压根不管内存越界的情况,甚至连个错误码都不返回。   例如:    sprintf(data.buf, “%d”, );    输出:   sizeof(Data) = 5 buf = 烫烫烫 guard = 15 ————— ret = 6 sizeof(Data) = 5 buf = guard = 53 memory has been broken. —————   总结:以上三组实验结果,在Windows和Linux下均可以得到验证,可见sprintf函数的安全系数几乎为0,不推荐大家使用。   vsprintf的行为与sprintf一样。       二、sprintf_s(Windows only)   为了弥补sprintf函数的不足,高版本的MSVC环境中引入了sprintf_s函数,在调用的时候支持用户传入目标内存的长度,函数原型可以简略的表示为:    int sprintf_s(char *buf, size_t buf_size, const char *format, …);    1.当源数据的长度【小于】len,sprintf把数据完整的写到目标内存,并保证尾部以0结尾,返回写入的字节数。此时该函数的行为是安全的。   例如:    sprintf_s(data.buf, len, “%d”, 12);    输出:   sizeof(Data) = 5 buf = 烫烫烫 guard = 15 ————— ret = 2 sizeof(Data) = 5 buf = 12 guard = 15 —————   2.当源数据的长度【等于】或者【大于】len的时候,调用此函数将会触发断言。Debug模式下会弹出运行时错误提示框,告诉用户”Buffer too small”;Release模式下程序会直接崩溃。   例如:    sprintf_s(data.buf, len, “%d”, 1234);    Debug模式下执行,会触发assert,如下图:   
printf和println_void与int的区别   总结:sprintf_s函数只能在Windows下使用,虽然不会出现写坏内存的情况,但是会触发assert,导致程序中断,使用起来也要慎重。   vsprintf_s的行为与sprintf_s一样。       三、_snprintf(Windows only)   也许是觉得sprintf_s也不够安全,MSVC环境中还引入了一个名为_snprintf的函数,其函数原型和sprintf_s类似,可以表示为:    int _snprintf(char *buf, size_t buf_size, const char *format, …);    其表现行为如下:   例1,当源数据的长度【小于】len,能保证完整写入,并以0结尾,返回实际写入的字节数:    _snprintf(data.buf, len, “%d”, 12);    输出:   sizeof(Data) = 5 buf = 烫烫烫 guard = 15 ————— ret = 2 sizeof(Data) = 5 buf = 12 guard = 15 —————   例2,当源数据的长度【等于】len,能保证完整写入,结尾不做任何处理,返回实际写入的字节数:    _snprintf(data.buf, len, “%d”, 1234);    输出:   sizeof(Data) = 5 buf = 烫烫烫 guard = 15 ————— ret = 4 sizeof(Data) = 5 buf = 1234烫烫烫 guard = 15 —————   例3,当源数据的长度【大于】len,最多写入【len】个字符,结尾不错任何处理,返回【-1】:    _snprintf(data.buf, len, “%d”, );    输出:   sizeof(Data) = 5 buf = 烫烫烫 guard = 15 ————— ret = -1 sizeof(Data) = 5 buf = 1234烫烫烫 guard = 15 —————   总结:_snprintf函数只能在Windows下使用,最多写入【size】个字符,永远不破坏内存,也不会触发中断,但不能保证目标内存以0结尾。通过返回值可以知道函数调用是否成功,返回值>=0的时候,表示调用成功,返回了实际写入的字符数;返回值为-1的时候,表示目标内存太小,导致调用失败,但是已经尽力做了填充。   _vsnprintf的行为与_snprintf一样。       四、snprintf(Linux/Windows)   Linux下的函数原型为:    int snprintf(char *str, size_t size, const char *format, …);    这个函数在Windows和Linux下均可以使用,并且行为一致。即:最多写入【size-1】个字符到目标内存,并保证以0结尾。返回值是【应该写入的字节数】,而不是【实际写入的字节数】   例1,当源数据的长度【小于】len,能保证完整写入,并以0结尾,返回实际写入的字节数:    snprintf(data.buf, len, “%d”, 12);    输出:   sizeof(Data) = 5 buf = 烫烫烫 guard = 15 ————— ret = 2 sizeof(Data) = 5 buf = 12 guard = 15 —————   例2:当源数据的长度【等于】len,实际上只写入了【len-1】个字符,最后一个字符用0填充,但返回值却是【len】:    snprintf(data.buf, len, “%d”, 1234);    输出:   sizeof(Data) = 5 buf = 烫烫烫 guard = 15 ————— ret = 4 sizeof(Data) = 5 buf = 123 guard = 15 —————   例3,当源数据的长度【大于】len,最多也只写入【len-1】个字符,最后一个字符用0填充,但返回值却是【应该要写入的字节数】:    snprintf(data.buf, len, “%d”, );    输出:   sizeof(Data) = 5 buf = 烫烫烫 guard = 15 ————— ret = 6 sizeof(Data) = 5 buf = 123 guard = 15 —————   总结:snprintf函数,可以在Linux/Windows双平台下使用,最多写入【size-1】个字符,永远不会破坏内存,也不会触发中断,并总能保证目标内存能以0结尾。唯一的问题是返回值不可靠,无法推断调用是否失败。   vsnprintf的行为与snprintf一样。   写到这里,sprintf系列的相关函数都讲完了,貌似没有一个完美的函数。不过既然知道了它们的具体行为,就可以根据应用场景挑选适合的函数。       补充:既然已经写到这儿了,就顺便利用这个机会顺便把strcpy函数簇也研究一下吧。   测试代码:   int main() { Data data; data.Display(); const char * ret = strncpy(data.buf, “”, len); std::cout << “ret = ” << ret << std::endl; data.Display(); std::cin.get(); return 0; }   一、strcpy(Linux/Windows)   函数原型为:char *strcpy(char *dest, const char *src);   最古老的字符串拷贝函数,原理很简单,从源字符串依次拷贝字符到目标地址,直到遇到0为止,如遇到内存重叠的时候,需要特殊处理。总是返回实际写入的字符数,不会处理内存越界的情况,也是毫无安全性,在此不做赘述。   二、strcpy_s(Windows only)   是Windows独有的函数,原型可以描述为:   int strcpy_s(char *dest, size_t size, const char *src);   注意返回值不再是目标字符串的首地址,而是一个int。   当源字符串长度【小于】或【等于】目标内存的时候,此函数可以安全执行,返回值为【0】,当源字符串长度【大于】目标内存的时候,此函数会触发assert断言,导致程序中断。这个函数不会导致内存破坏。   三、strncpy_s(Windows only)   是Windows独有的函数,原型可以描述为:   int strncpy_s(char *dest, size_t dest_size, const char *src, size_t count);   返回值也是一个int。   这个函数除了能指定目标内存的大小,还能指定拷贝的字符数量,相当于做了双重保护。   但是注意必须满足【count <= dest_size – 1】,这个函数才能正确调用,否则也会触发assert中断。   四、strncpy(Linux/Windows)   函数原型:char *strncpy(char *dest, const char *src, size_t size);   行为与strcpy类似,从源字符串依次拷贝字符到目标地址,直到遇到0或者目标内存已写满为止,最多拷贝【size】个字符。这个函数不会破坏内存,也不会导致程序中断,但是无法保证目标字符串以0结尾。   例如:   strncpy(data.buf, “12345”, len);   输出:   sizeof(Data) = 5 buf = 烫烫烫 guard = 15 ————— ret = 1234烫烫烫 sizeof(Data) = 5 buf = 1234烫烫烫 guard = 15 —————    

2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/71360.html

(0)
上一篇 2024年 8月 6日
下一篇 2024年 8月 6日

相关推荐

关注微信