今天在微软家的strncpy_s
函数上栽了跟头,记录一下
char *strncpy(char *strDest, const char *strSource, size_t count );
errno_t strncpy_s(char *strDest, size_t numberOfElements, const char *strSource, size_t count);
简言之,在已经从strSource
拷贝了多达count
个非0字符后:
strncpy
函数不会往strDest
追加'\0'
strncpy_s
函数会试图往strDest
追加'\0'
摘抄一下MSDN上关于strncpy
函数的说明:
The strncpy function copies the initial count characters of strSource to strDest and returns strDest.
If count is less than or equal to the length of strSource, a null character is not appended automatically to the copied string.
If count is greater than the length of strSource, the destination string is padded with null characters up to length count. The behavior of strncpy is undefined if the source and destination strings overlap.
翻译过来:
strncpy函数拷贝strSource原始的count个字符到strDest,并且返回strDest。
如果count小于等于strSource的长度,不会自动追加一个空字符到拷贝至的字符串。
再来看看MSDN上关于strncpy_s
函数的说明:
These functions try to copy the first D characters of strSource to strDest, where D is the lesser of count and the length of strSource. If those D characters will fit within strDest (whose size is given as numberOfElements) and still leave room for a null terminator, then those characters are copied and a terminating null is appended; otherwise, strDest[0] is set to the null character and the invalid parameter handler is invoked, as described in Parameter Validation.
……
翻译过来:
这些函数 [ 1 ] ^{[1]} [1]尝试从 strSource 拷贝前D个字符到strDest,其中D是count和strSource中的较小者。如果strDest(它的大小由numberOfElements给出)能够装下那D个字符,并且仍然留有空间来放置一个空的结尾标志,那么那些字符会被拷贝并且会追加一个空的结尾标志;否则的话, strDest[0] 被设置为空字符,并且会调用无效参数处理机制,如 Parameter Validation中所述。
注[1]:在MSDN原文中"这些函数"代指一家子*ncpy_s形式的函数,strncpy_s
函数是其中之一
从上面的两段说明可以看出,strncpy
和strncpy_s
的行为是有着很大不同的。
在使用strncpy
函数时我们可能习惯于把count
设置为目的地址strDest
的缓冲区大小,这是没有问题的,因为strncpy
函数总是老老实实地就往目的地址拷贝恰好count
个字符,如果拷贝过程到达了源字符串的结束标志'\0'
,后面直接用填充往目的地址若干个'\0'
,直到写入了满count
个字符,否则如果拷贝了多达count
个非0字符,就停止拷贝,不会往目的地址追加'\0'
。
而strncpy_s
函数则不一样,它总是试图往目的地址追加'\0'
,如果在调用strncpy_s
函数时把count
设置为目的地址strDest
的缓冲区大小,可能会出错。因为如果源字符串字符个数(不计入结尾的'\0'
)大于等于count
,那么strncpy_s
函数发现:如果拷贝count
个非0字符并且追加一个'\0'
,这个追加的'\0'
其实就越界了,所以strncpy_s
函数不会进行拷贝,并且会抛出异常。
所以在调用strncpy_s
函数时,用来说明strDest
缓冲区的大小的那个参数numberOfElements
必须满足——
numberOfElements
≥ \geq ≥ 1+ min(count
,strlen(strDest)
)
已测试于VS2019
分析下面的示例程序
#include
#include
int main()
{
char buff[11] = "cccccccccc"; //15个'c'
strncpy(buff, "hello", 5); //buff内容变为helloccccc\0
printf("%s\n", buff); //打印helloccccc
strncpy_s(buff, 6, "world", 5); //buff内容为world\0cccc\0
printf("%s\n", buff); //打印world
strncpy_s(buff, 5, "abcde", 5); //抛出异常
return 0;
}
刚开始buff缓冲区的内容为
偏移 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
内容 | ‘c’ | ‘c’ | ‘c’ | ‘c’ | ‘c’ | ‘c’ | ‘c’ | ‘c’ | ‘c’ | ‘c’ | ‘\0’ |
第7行的strncpy
函数调用拷贝’h’、‘e’、‘l’、‘l’、'o’共5个字符到buff,不会补'\0'
。
这时buff缓冲区的内容为
偏移 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
内容 | ‘h’ | ‘e’ | ‘l’ | ‘l’ | ‘o’ | ‘c’ | ‘c’ | ‘c’ | ‘c’ | ‘c’ | ‘\0’ |
第8行的printf
会打印helloccccc
第9行的strncpy_s
函数调用拷贝了’w’、‘o’、‘r’、‘l’、'd’这5个字符串后,还追加了一个'\0'
,实际上是往buff写入了6个字节。
这时buff缓冲区的内容为
偏移 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
内容 | ‘w’ | ‘o’ | ‘r’ | ‘l’ | ‘d’ | ‘\0’ | ‘c’ | ‘c’ | ‘c’ | ‘c’ | ‘\0’ |
第10行的printf
会打印world
第11行的strncpy_s
函数调用,由于abcde加上一个'\0'
有6个字符,超过了指定的缓冲区大小5,所以strncpy_s
不会进行拷贝,并且会抛出异常。
弹出窗口中"Buffer is too small"这句话也直接说明了,抛出这个异常是因为缓冲区太小了(要写入6个字符,而缓冲区大小为5)。