#include <stdio.h> #include <string.h> typedef unsigned char uchar; #define BUF_SIZE 10 // 缓冲区大小 #define CLEAR_SIZE (BUF_SIZE+2) // 操作的缓冲区大小, 需要全部重置 void printRuler(int len) { putchar('\n'); for(int i = 1; i <= len; i++) { printf("%02d ", i); if(i == BUF_SIZE) printf("\t|\t"); } putchar('\n'); } void dis(char *buf, int len) { for(int i = 0; i < len; i++) { printf("%02x ", (uchar)buf[i]); if(i == BUF_SIZE - 1) printf("\t|\t"); } // puts("\n"); // 另起一行输出字符串并换行, 不会接在原来的后面 putchar('\n'); // 直接输出字符 printf("buf=[%s]\n\n", buf); } int main() { char buf[BUF_SIZE]; memset(buf, 0xcc, CLEAR_SIZE); dis(buf, CLEAR_SIZE); printRuler(CLEAR_SIZE); printf("-------------------------------------- snprintf\n"); memset(buf, 0xcc, CLEAR_SIZE); snprintf(buf, BUF_SIZE, "%s", "12345678"); // 未溢出 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); snprintf(buf, BUF_SIZE, "%s", "123456789"); // 未溢出, 刚好填满 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); snprintf(buf, BUF_SIZE, "%s", "123456789A"); // 未溢出, 被截断1个字符 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); snprintf(buf, BUF_SIZE, "%s", "123456789AB"); // 未溢出, 被截断2个字符 dis(buf, CLEAR_SIZE); ////////////////////////////////////////////////////////////////////////////// printRuler(CLEAR_SIZE); printf("-------------------------------------- sprintf\n"); memset(buf, 0xcc, CLEAR_SIZE); sprintf(buf, "%s", "12345678"); // 未溢出 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); sprintf(buf, "%s", "123456789"); // 刚刚好 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); sprintf(buf, "%s", "123456789A"); // 溢出1个字符 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); sprintf(buf, "%s", "123456789AB"); // 溢出2个字符 dis(buf, CLEAR_SIZE); return 0; } /* 运行情况: D:\profile\Desktop\test>make g++ -o a.exe a.cpp D:\profile\Desktop\test>a cc cc cc cc cc cc cc cc cc cc | cc cc buf=[烫烫烫烫烫烫@] 01 02 03 04 05 06 07 08 09 10 | 11 12 -------------------------------------- snprintf 31 32 33 34 35 36 37 38 00 cc | cc cc buf=[12345678] 31 32 33 34 35 36 37 38 39 00 | cc cc buf=[123456789] 31 32 33 34 35 36 37 38 39 00 | cc cc buf=[123456789] 31 32 33 34 35 36 37 38 39 00 | cc cc buf=[123456789] 01 02 03 04 05 06 07 08 09 10 | 11 12 -------------------------------------- sprintf 31 32 33 34 35 36 37 38 00 cc | cc cc // 空间有余 buf=[12345678] 31 32 33 34 35 36 37 38 39 00 | cc cc // 刚好填充満 buf=[123456789] 31 32 33 34 35 36 37 38 39 41 | 00 cc // 溢出1个字符 buf=[123456789A] 31 32 33 34 35 36 37 38 39 41 | 42 00 // 溢出2字符 buf=[123456789AB] 结论: 1. sprintf和snprintf都会在字符串末尾加上'\0' 2. snprintf比sprintf安全,即不会造成缓冲区溢出 */
以上的测试环境是GNUMake(windows), 和Linux.
===============================================================================
以下的代码测试环境是windows vc6和vc2010
#define _CRT_SECURE_NO_WARNINGS // 针对vc2010添加 #include <stdio.h> #include <string.h> typedef unsigned char uchar; #ifdef WIN32 #define snprintf _snprintf // windows平台无snprintf, 但是有_snprintf #endif #define BUF_SIZE 10 // 缓冲区大小 #define CLEAR_SIZE (BUF_SIZE+2) // 操作的缓冲区大小, 需要全部重置 void printRuler(int len) { putchar('\n'); for(int i = 1; i <= len; i++) { printf("%02d ", i); if(i == BUF_SIZE) printf("\t|\t"); } putchar('\n'); } void dis(char *buf, int len) { for(int i = 0; i < len; i++) { printf("%02x ", (uchar)buf[i]); if(i == BUF_SIZE - 1) printf("\t|\t"); } // puts("\n"); // 另起一行输出字符串并换行, 不会接在原来的后面 putchar('\n'); // 直接输出字符 printf("buf=[%s]\n\n", buf); } int main() { char buf[BUF_SIZE]; memset(buf, 0xcc, CLEAR_SIZE); dis(buf, CLEAR_SIZE); printRuler(CLEAR_SIZE); printf("-------------------------------------- snprintf\n"); memset(buf, 0xcc, CLEAR_SIZE); snprintf(buf, BUF_SIZE, "%s", "12345678"); // 未溢出 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); snprintf(buf, BUF_SIZE, "%s", "123456789"); // 未溢出, 刚好填满 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); snprintf(buf, BUF_SIZE, "%s", "123456789A"); // 未溢出, 被截断1个字符 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); snprintf(buf, BUF_SIZE, "%s", "123456789AB"); // 未溢出, 被截断2个字符 dis(buf, CLEAR_SIZE); ////////////////////////////////////////////////////////////////////////////// printRuler(CLEAR_SIZE); printf("-------------------------------------- sprintf\n"); memset(buf, 0xcc, CLEAR_SIZE); sprintf(buf, "%s", "12345678"); // 未溢出 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); sprintf(buf, "%s", "123456789"); // 刚刚好 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); sprintf(buf, "%s", "123456789A"); // 溢出1个字符 dis(buf, CLEAR_SIZE); memset(buf, 0xcc, CLEAR_SIZE); sprintf(buf, "%s", "123456789AB"); // 溢出2个字符 dis(buf, CLEAR_SIZE); return 0; } /* 运行情况(vc6/vc2010): cc cc cc cc cc cc cc cc cc cc | cc cc buf=[烫烫烫烫烫烫?] 01 02 03 04 05 06 07 08 09 10 | 11 12 -------------------------------------- snprintf 31 32 33 34 35 36 37 38 00 cc | cc cc // 未溢出时会填充字符串结束符('\0') buf=[12345678] 31 32 33 34 35 36 37 38 39 00 | cc cc // 未溢出时会填充字符串结束符('\0') buf=[123456789] 31 32 33 34 35 36 37 38 39 41 | cc cc // 溢出了将不会填充字符串结束符('\0') buf=[123456789A烫?] 31 32 33 34 35 36 37 38 39 41 | cc cc // 溢出了将不会填充字符串结束符('\0') buf=[123456789A烫?] 01 02 03 04 05 06 07 08 09 10 | 11 12 -------------------------------------- sprintf 31 32 33 34 35 36 37 38 00 cc | cc cc buf=[12345678] 31 32 33 34 35 36 37 38 39 00 | cc cc buf=[123456789] 31 32 33 34 35 36 37 38 39 41 | 00 cc buf=[123456789A] 31 32 33 34 35 36 37 38 39 41 | 42 00 buf=[123456789AB] 结论: 1. windows上无snprintf,但是有_snprintf可以达到同样的功能,但是细节之处略有不同 2. 未溢出时,sprintf和snprintf都会在字符串末尾加上'\0'; 3. 超出缓冲区大小时,_snprintf不会造成溢出,但是不会在缓冲区中添加字符结束符 4. sprintf始终会在字符串末尾添加结束符,但是存在缓冲区溢出的情况 5. _snprintf比sprintf安全,即不会造成缓冲区溢出 6. vc6中对于_snprintf缓冲区溢出时不会出现崩溃信息,但是在vc2010中却会出现 */