几个重要库函数的实现-strcpy,strncpy,memcpy,memset

本文详细解析了C++中常用的内存操作函数,包括strcpy、strncpy、memcpy和memset的实现细节及注意事项,重点讨论了如何避免常见的错误,并提供了实际的代码示例。

原文来自:http://blog.youkuaiyun.com/hyhyl1990/article/details/7958263

面试官很喜欢让求职者写一些常用库函数的实现,有很多是和字符串相关的,有一些是关于内存拷贝的。一般,常会让写的函数有以下几个:

strcpy , strncpy, memcpy。

memset一般不会让去写,但这个函数也很有特点,有很多容易用错的地方。一并总结吧。

1.  strcpy

strcpy函数的原型是:

char * strcpy(char* dest, const char* src)

strcpy的实现经常要注意的细节是:

(1)判断地址是否为空,个人感觉可以使用断言

(2)参数只有两个地址,没有拷贝的长度。拷贝到'\0‘时就会终止,要保证最终dest末尾是'\0'。

(3)要保证目标字串的长度足够,能够容纳原串的长度。

(4)因为拷贝是dest会移动,而最终要返回的是拷贝后字符串的起始地址,因此要先保存dest的地址,便于最终返回。

          在实现这一点时,有两种方法。 char* temp=dest; 拷贝时移动dest返回temp,或者拷贝时移动temp返回dest,不知道哪个是对的。感觉两个都是没有问题的

 其中一种实现方式:

[cpp]  view plain copy
  1. char* mystrcpy(char* dest,const char* src)  
  2. {  
  3.     assert(dest!=NULL && src!=NULL);  
  4.   
  5.     char* temp=dest;  
  6.   
  7.     while((*temp++ = *src++ )!='\0')  
  8.     {}  
  9.   
  10.     return dest;  
  11. }  


2. strncpy

strncpy的功能和strcpy相似,只是它复制时多了一个终止条件。即是未遇到原串的'\0’,如果已经复制了n个字符(n为提供的参数长度),复制同样会终止。

strcpy的实现要注意的细节也基本适用于strncpy的实现。

 

实现方式:

[cpp]  view plain copy
  1. char* mystrncpy(char* dest, const char* src, int len)  
  2. {  
  3.     assert(dest!=NULL && src!=NULL);  
  4.   
  5.     char* temp=dest;  
  6.     int i=0;  
  7.   
  8.     while(i++ < len  && (*temp++ = *src++)!='\0')  
  9.     {}  
  10.   
  11.     if(*(--temp)!='\0')  
  12.         *temp='\0';  
  13.   
  14.     return dest;  
  15. }  
[cpp]  view plain copy
  1. 注意:刚开始我写strncpy的实现时,把while(i++ < len && (*temp++ = *src++)!='\0')写成了while( (*temp++ = *src++)!='\0' && i++ < len); 导致最后多复制了一个字符,明白为什么吧。。  

 

3. memcpy

memcpy和strncpy有些类似,但也有本质的不同。

(1)strncpy只能复制字符串,但memcpy对类型没有要求。

(2)strncpy有两个终止条件,memcpy只有一个终止条件,那就是复制n个字节。(n是memcpy的第三个参数)

(3)要特别注意目的地址和源地址重合的问题,拷贝前要加以判断。

(4)实现这个函数时一般要把原来的指针类型转换成char*,这样每次移动都是一个字节。

 

实现方式:(考虑了两个地址空间是否会有重叠)

[cpp]  view plain copy
  1. void* mymemcpy(void* dest, void* src, int len)  
  2. {  
  3.     int i=0;  
  4.     char* tempdest=(char*)dest;  
  5.     char* tempsrc=(char*)src;  
  6.   
  7.     if(tempdest<tempsrc || tempdest>(tempsrc+len-1))  
  8.     {  
  9.         while(i<len)  
  10.         {  
  11.             *tempdest++ = *tempsrc++;  
  12.             i++;  
  13.         }  
  14.     }  
  15.     else  
  16.     {  
  17.         tempdest+=len;  
  18.         tempsrc+=len;  
  19.         i=len;  
  20.         while(i>0)  
  21.         {  
  22.             *tempdest-- = *tempsrc--;  
  23.             i--;  
  24.         }  
  25.     }  
  26.     return dest;  
  27. }  

注意,memcpy是对内存的拷贝,对其他安全性不做考虑。用户在使用这个函数时要小心,比如用它来拷贝字符串(当然如果是字符串拷贝肯定是用strncpy)就要注意末尾的\0字符之类的。

4. memset

memset函数的原型是:

void *memset(void *s, int ch, size_t n)

作用是把s所指向的地址开始的n个字节的内容全部置位ch所指定的ASCII值。

一般经常用memset对某段内存空间置零。

 

经常会出现的一个问题:在C++中,为什么不提倡在构造函数中使用:memset(this,0,sizeof(*this))

原因: 在C++中,如果类中都是基本类型的数据成员并且没有虚函数和虚继承的话,使用memset这样用到没有太多影响。

如果有虚函数,memset会把虚表指针等全部置零,对类会产生破坏

<think> 我们正在讨论的是在`strncpy`之前是否需要使用`memset`的问题。根据C标准库函数的行为,我们可以分析如下: 1. **`strncpy`函数的行为特点**: - `strncpy`用于从源字符串复制最多`n`个字符到目标字符串。 - 如果源字符串的长度小于`n`,则`strncpy`会将剩余部分用空字符(`\0`)填充,直到复制了`n`个字符。 - 如果源字符串的长度大于或等于`n`,则`strncpy`会复制`n`个字符,但**不会**在目标字符串的末尾添加空字符(也就是说,目标字符串可能不以`\0`结尾)。 2. **使用`memset`的考虑**: - `memset`通常用于将一段内存区域设置为特定的值(例如,全部设置为0)。 - 在`strncpy`之前使用`memset`,可能是为了将目标内存区域清零,以确保目标字符串以`\0`结尾(特别是在复制的字符数不足时)或者清除旧数据。 3. **是否需要使用`memset`**: - **一般情况不需要**:因为当源字符串长度小于`n`时,`strncpy`会自动用`\0`填充剩余部分。因此,即使目标内存区域之前有数据,只要`strncpy`的`n`参数设置正确(比如等于目标缓冲区的大小),那么未填充的部分会被`strncpy`自动填充为`\0`,从而形成一个完整的以`\0`结尾的字符串。 - **特殊情况可能需要**: - **安全性要求**:如果目标缓冲区之前存储了敏感信息,为了安全需要清零,那么可以在`strncpy`之前使用`memset`清零。 - **目标缓冲区大小大于`n`**:如果目标缓冲区的大小大于`n`,那么`strncpy`只会覆盖前`n`个字节,后面的部分保持不变。如果这部分旧数据需要清零,则可以使用`memset`(但注意,`memset`的目标应该是整个缓冲区,而不仅仅是`n`个字节)。 - **确保字符串以`\0`结尾的另一种方法**:尽管`strncpy`在源字符串长度小于`n`时会自动填充`\0`,但若源字符串长度大于等于`n`,则目标字符串不会以`\0`结尾。因此,为了确保目标字符串以`\0`结尾,一种常见的做法是:在调用`strncpy`之前或之后,手动在目标缓冲区的末尾添加一个`\0`。例如: ```c char dest[100]; // 方法1:在strncpy之前将整个缓冲区清零(这样即使复制后没有添加\0,最后也有\0,因为清零了整个缓冲区) memset(dest, 0, sizeof(dest)); strncpy(dest, src, sizeof(dest)-1); // 注意:这里用sizeof(dest)-1,确保最后一个位置是0(由前面的memset保证) // 方法2:在strncpy之后手动添加\0 strncpy(dest, src, sizeof(dest)-1); dest[sizeof(dest)-1] = '\0'; ``` 这里,方法1使用了`memset`,方法2没有使用。方法1的优点是整个缓冲区都被初始化了,而方法2只设置了最后一个字符。 4. **引用内容分析**: - 引用[1]提到:`memcpy`是对内存的拷贝,不考虑安全性,而字符串拷贝应该使用`strncpy`。同时提到`memset`用于初始化(如清零)[^1]。 - 引用[2]说明:`memset`一般用于数组或结构体的初始化,普通变量直接初始化即可[^2]。 - 引用[3]举例:使用`memset`时,如果目标是指针(而不是数组),那么`sizeof(指针)`得到的是指针的大小而不是指向的缓冲区大小,这样可能导致错误[^3]。因此,使用`memset`时一定要确保长度参数正确。 - 引用[4]指出:`memset`将指定内存的前n个字节设置为特定的值[^4]。 - 引用[5]提到:`memcpy`在内存重叠时行为未定义[^5](但这个问题与`strncpy`无关)。 5. **结论**: - 在`strncpy`之前使用`memset`并不是必须的,因为`strncpy`在复制时会覆盖目标缓冲区的前`n`个字节,并且在源字符串长度小于`n`时会自动填充`\0`。 - 但是,如果希望整个目标缓冲区都被初始化(例如,确保缓冲区中未填充部分也是0),那么可以在`strncpy`之前使用`memset`清零整个缓冲区。这可以看作是一种防御性编程。 - 另一种更常见的做法是:在调用`strncpy`之后,手动在目标缓冲区的末尾(位置`n-1`)添加一个`\0`,以确保字符串的终止(无论源字符串是否被截断)。 6. **代码示例**: ```c #include <string.h> int main() { char dest[100]; const char *src = "Hello, World!"; // 方法1:使用memset(可选) // memset(dest, 0, sizeof(dest)); // 初始化整个缓冲区为0 // strncpy(dest, src, sizeof(dest)-1); // 复制,并确保最后一个位置是0(如果之前memset了,这里减1可以省略,但为了安全还是保留) // 方法2:不使用memset,但手动添加结束符 strncpy(dest, src, sizeof(dest)-1); dest[sizeof(dest)-1] = '\0'; // 确保字符串结束 return 0; } ``` 7. **建议**: - 对于固定大小的缓冲区,如果希望整个缓冲区都被初始化(不仅仅是字符串部分),那么使用`memset`清零整个缓冲区是合理的。 - 如果只关心字符串的正确终止,那么更高效的做法是:在`strncpy`之后,手动在目标缓冲区的最后一个位置添加`\0`,避免对整个缓冲区的清零操作(尤其是当缓冲区很大时)。 因此,回答用户的问题:**在`strncpy`之前使用`memset`不是必要的,但根据具体需求可以选择使用。** --- ### 相关问题 1. `strncpy`函数在复制字符串时如何确保目标字符串以`\0`结尾? 2. `strncpy`和`strcpy`函数的主要区别是什么?在什么情况下应该使用`strncpy`? 3. 在使用`strncpy`时,如何避免缓冲区溢出的问题? 4. 除了`strncpy`,还有哪些安全的字符串处理函数(如`snprintf`)? </think> ### 使用`strncpy`前是否需要`memset`? **核心结论:通常不需要,但需根据目标内存状态和安全性需求决定**。以下是详细分析: --- #### 1. **`strncpy`的行为特点** - `strncpy`设计用于**安全复制字符串**: - 从源字符串复制最多`n`个字符到目标内存。 - **若源字符串长度小于`n`**:自动用`\0`填充目标内存剩余部分(确保字符串终止)[^1][^4]。 - **若源字符串长度≥`n`**:复制`n`个字符但**不会添加终止符`\0`**(目标字符串可能非正常终止)[^1][^5]。 - **关键特性**: - 覆盖目标内存的前`n`字节,不影响后续内容。 - 不依赖目标内存的初始状态(直接覆盖旧数据)[^1][^4]。 --- #### 2. **何时不需要`memset`** - **目标内存已分配且无残留敏感数据**: ```c char dest[100]; // 未初始化,含随机值 strncpy(dest, "Hello", 5); // 复制后:dest = "Hello" + 95字节随机值(非安全!) ``` 此时`strncpy`不会填充`\0`(源字符串长度=5,`n=5`),但**无需`memset`**,因后续可通过手动添加终止符: ```c dest[5] = '\0'; // 显式终止字符串 ``` - **目标内存已清零或可接受残留数据**: 若目标内存已初始化(如全局变量默认为0),或残留数据不影响逻辑,则`memset`冗余[^2][^4]。 --- #### 3. **何时需要`memset`** - **安全敏感场景**(防数据泄漏): ```c char password_buf[100]; // 曾存储旧密码 memset(password_buf, 0, sizeof(password_buf)); // 先清空残留数据 strncpy(password_buf, new_pwd, sizeof(new_pwd)); ``` 避免旧密码残留内存中[^3]。 - **目标内存未完全覆盖且需初始化**: ```c char dest[100] = "OldData"; // 含旧数据 memset(dest, 0, sizeof(dest)); // 全缓冲区清零 strncpy(dest, "Hi", 2); // 结果:dest = "Hi\0"(剩余97字节为0) ``` 确保未复制部分为`\0`[^1][^4]。 - **避免非终止字符串**: 若`strncpy`未添加`\0`(源长度≥`n`),且目标内存未初始化,可能读取到非法字符: ```c char dest[5]; memset(dest, 0, sizeof(dest)); // 保证终止符存在 strncpy(dest, "LongString", 4); // 复制4字符(无\0) printf("%s", dest); // 安全:因memset已清零 ``` --- #### 4. **常见错误与正确实践** - **错误用法**: ```c char *file_path = malloc(100); memset(file_path, 0, sizeof(file_path)); // 错误!仅清空指针本身(4字节) strncpy(file_path, "path/to/file", 50); ``` **正确做法**:`memset(file_path, 0, 100);` [^3]。 - **替代方案**(无需`memset`): ```c // 手动添加终止符(更高效) strncpy(dest, src, dest_size - 1); dest[dest_size - 1] = '\0'; ``` --- #### 5. **总结建议** | **场景** | **是否需要`memset`** | **理由** | |------------------------------------|----------------------|--------------------------------------------------------------------------| | 目标内存无敏感数据且后续手动加`\0` | 否 | `strncpy`直接覆盖,手动终止更高效[^1][^4] | | 目标内存含敏感残留 | 是 | 防止信息泄漏[^3] | | 需确保整个缓冲区初始化 | 是 | 避免未覆盖部分为随机值(如网络协议数据)[^2][^4] | | 源字符串长度≥`n` | 可选 | 若目标内存未初始化,建议用`memset`或手动加`\0`(防读取越界)[^1][^5] | > **最佳实践**: > - 优先在`strncpy`后**手动添加终止符**`\0`,而非依赖`memset`。 > - 仅当需清除敏感数据或初始化整个缓冲区时使用`memset`。 --- ### 相关问题 1. `strncpy`和`strcpy`的主要区别是什么?如何避免目标字符串无终止符? 2. 在哪些场景下应使用`memset`初始化内存?它与`calloc`有何不同? 3. 如何安全处理包含敏感数据的内存缓冲区(如密码)? 4. `strncpy`在复制长字符串时可能导致什么问题?是否有更安全的替代函数? [^1]: `strncpy`用于字符串复制,但需注意终止符处理 [^2]: `memset`适用于数组/结构体初始化,普通变量无需使用 [^3]: 使用`memset`时需确保长度参数正确(非指针大小) [^4]: `memset`用于填充内存,`strncpy`用于字符串复制 [^5]: `strncpy`在源长度≥`n`时不添加终止符
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值