在进行程序设计时,免不了在函数之间进行数据复制。常见的方法主要有:
函数原型:
1.char *strcpy(char* dest, const char *src)
把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间
1.1.strcpy函数在拷贝时也会把源字符串中的'\0'拷贝到目标字符串中作为拷贝结束的标志。
1.2.目标空间必须足够大存放的下源字符串,放不下源字符串中的内容,程序会崩溃。
原型:
//strcpy(dest,src)把从src地址开始且含有null结束符的字符串复制到以dest开始的地址空间
char *strcpy(char *strDest, const char *strSrc)
{
char *res=strDest;
assert((strDest!=NULL)&&(strSrc!=NULL));
while((*strDest=*strSrc)!='\0')
{
strDest++;
strSrc++;
}
return res;
}
2.void *memcpy(void *dest, const void *src, size_t n);
从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。
window下函数原型:
void* __cdecl memcpy(void* dst,const void* src,size_t count)
{
void*ret=dst;
#if defined(_M_MRX000)||defined(_M_ALPHA)||defined(_M_PPC)
{
extern void RtlMoveMemory(void *,const void *,size_t count);
RtlMoveMemory(dst,src,count);
}
#else /*defined(_M_MRX000)||defined(_M_ALPHA)||defined(_M_PPC)*/
/*
*copy from lower addresses to higher addresses
*/
while(count--){
*(char *)dst = *(char *)src;
dst = (char *)dst+1;
src = (char *)src+1;
}
#endif /*defined(_M_MRX000)||defined(_M_ALPHA)||defined(_M_PPC)*/
return (ret);
}
linux系统下:
void *memcpy(void *to, const void *from, size_t n)
{ //记录拷贝目的位置,为了返回拷贝内容的首地址
void *xto = to;
size_t temp, temp1;
//判断拷贝的字节数
if (!n)
return xto; //
if ((long)to & 1) {
char *cto = to;
const char *cfrom = from;
*cto++ = *cfrom++;
to = cto;
from = cfrom;
n--;
}
if (n > 2 && (long)to & 2) {
short *sto = to;
const short *sfrom = from;
*sto++ = *sfrom++;
to = sto;
from = sfrom;
n -= 2;
}
temp = n >> 2;
if (temp) {
long *lto = to;
const long *lfrom = from;
#if defined(CONFIG_M68000) || defined(CONFIG_COLDFIRE)
for (; temp; temp--)
*lto++ = *lfrom++;
#else
asm volatile (
" movel %2,%3\n"
" andw #7,%3\n"
" lsrl #3,%2\n"
" negw %3\n"
" jmp %%pc@(1f,%3:w:2)\n"
"4: movel %0@+,%1@+\n"
" movel %0@+,%1@+\n"
" movel %0@+,%1@+\n"
" movel %0@+,%1@+\n"
" movel %0@+,%1@+\n"
" movel %0@+,%1@+\n"
" movel %0@+,%1@+\n"
" movel %0@+,%1@+\n"
"1: dbra %2,4b\n"
" clrw %2\n"
" subql #1,%2\n"
" jpl 4b"
: "=a" (lfrom), "=a" (lto), "=d" (temp), "=&d" (temp1)
: "0" (lfrom), "1" (lto), "2" (temp));
#endif
to = lto;
from = lfrom;
}
if (n & 2) {
short *sto = to;
const short *sfrom = from;
*sto++ = *sfrom++;
to = sto;
from = sfrom;
}
if (n & 1) {
char *cto = to;
const char *cfrom = from;
*cto = *cfrom;
}
return xto;
}
strcpy和memcpy主要有三方面的区别。
其一:复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
其二:复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,如果空间不够,就会引起内存溢出。memcpy则是根据其第3个参数决定复制的长度。
其三:用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy,由于字符串是以“\0”结尾的,所以对于在数据中包含“\0”的数据只能用memcpy。
3.void *memmove( void* dest, const void* src, size_t count ); 由src所指内存区域复制count个字节到dest所指内存区域。
4.void *memset(void *s, int ch, size_t n); 将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s。
5.int sprintf(char *str, const char *format, ...);
功能:将格式化的数据写入某个字符串缓冲区
入参:format,输出字符串的格式化列表,比如%d、%s、%c等
入参:format对应的不定参数列表,与printf类似
出参:buffer,指向一段存储空间,用于存储格式化之后的字符串
返回值:返回写入buffer的字符数,出错则返回-1
5.1. 根据参数[format字符串]来转换并格式化数据
5.2. 将格式化结果复制到str指向的字符串数组
5.3. 直到出现字符串结束符['\0']为止
//sprintf用法举例
#include <stdio.h>
#include <stdlib.h>
int main()
{
char buffer[10];
const int a = 12345;
const char *b = "102938465839202";
sprintf(buffer, "%d", a); //将变量a按int类型打印成字符串,输出到buffer中
sprintf(buffer, "%d+%s", a, b); //将变量a和字符串b连接成一个字符串输出到buffer中
return 0;
}
程序分析:
如果输出到buffer的内容长度不超过10个字节,那么此时sprintf的操作是没有风险的;
如果超过了10个字节,那么就会导致buffer存储空间溢出,从存储位置上分析,buffer空间是一个栈空间,在它自己10个字节以外的空间是其他栈变量的存储空间,一旦sprintf将10字节外的其他空间也操作了,这就有可能破坏了其他栈变量的内容,程序崩溃发生。
6.int snprintf(char *str, size_t size, const char *format, ...);
6.1. 根据参数[format字符串]来转换并格式化数据
6.2. 将格式化结果复制到str指向的字符串数组
6.3. 直到出现字符串结束符['\0']或达到size指定大小为止(截断)
//snprintf用法
#include <stdio.h>
#include <stdlib.h>
int main()
{
char buffer[10];
const int a = 12345;
const char *b = "102938465839202";
snprintf(buffer, sizeof(buffer), "%d", a); //将变量a按int类型打印成字符串,输出到buffer中
snprintf(buffer, sizeof(buffer), "%d+%s", a, b); //将变量a和字符串b连接成一个字符串输出到buffer中
return 0;
}
分析:
1. windows上无snprintf,但是有_snprintf可以达到同样的功能,但是细节之处略有不同
windows下没有snprintf相关的库函数。
解决方案:声明snprintf在windows下对应的库函数。
#ifdef _WIN32
//#define snprintf _snprintf
#endif
#ifdef _MSC_VER
#define snprintf _snprintf
#endif
2. 未溢出时,sprintf和snprintf都会在字符串末尾加上'\0';
3. 超出缓冲区大小时,_snprintf不会造成溢出,只是不会在缓冲区中添加字符结束符
4. sprintf始终会在字符串末尾添加结束符,但是存在缓冲区溢出的情况
5. _snprintf比sprintf安全,即不会造成缓冲区溢出
|