strcpy写法及字符串指针问题

本文探讨了如何不使用库函数实现strcpy,并分析了实现过程中指针有效性检查的重要性。作者指出,检查指针是否为NULL时,不应使用简单的逻辑运算符,而应使用NULL常量,以提高代码可读性和维护性。此外,文章还强调了返回原始strDest值以支持链式表达式和避免内存泄漏的必要性,并提供了示例代码进行说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题是从baiyuxiong发的那个“出现频率最高的笔试题strcpy写法+分析zz”帖子开始的:

 

题目:
    已知strcpy函数的原型是:
        char * strcpy(char * strDest,const char * strSrc);
    1.不调用库函数,实现strcpy函数。
    2.解释为什么要返回char *。

    解说:
    1.strcpy的实现代码

        char * strcpy(char * strDest,const char * strSrc)

        {

                if ((strDest==NULL)||(strSrc==NULL)) //[1]

                        throw "Invalid argument(s)"; //[2]

                char * strDestCopy=strDest;  //[3]

                while ((*strDest++=*strSrc++)!='/0'); //[4]

                return strDestCopy;

        }


    错误的做法:
    [1]
    (A)不检查指针的有效性,说明答题者不注重代码的健壮性。
    (B)检查指针的有效性时使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),说明答题者 对C语言中类型的隐式转换没有深刻认识。在本例中char *转换为bool即是类型隐式转换,这种功能虽然灵活,但更多的是导致出错概率增大和维护成本升高。所以C++专门增加了bool、true、false 三个关键字以提供更安全的条件表达式。
    (C)检查指针的有效性时使用((strDest==0)||(strSrc==0)),说明答题者不知道使用常量的好处。直接使用字面常量(如本例中的 0)会减少程序的可维护性。0虽然简单,但程序中可能出现很多处对指针的检查,万一出现笔误,编译器不能发现,生成的程序内含逻辑错误,很难排除。而使用 NULL代替0,如果出现拼写错误,编译器就会检查出来。
    [2]
    (A)return new string("Invalid argument(s)");,说明答题者根本不知道返回值的用途,并且他对内存泄漏也没有警惕心。从函数中返回函数体内分配的内存是十分危险的做法,他 把释放内存的义务抛给不知情的调用者,绝大多数情况下,调用者不会释放内存,这导致内存泄漏。
    (B)return 0;,说明答题者没有掌握异常机制。调用者有可能忘记检查返回值,调用者还可能无法检查返回值(见后面的链式表达式)。妄想让返回值肩负返回正确值和异常 值的双重功能,其结果往往是两种功能都失效。应该以抛出异常来代替返回值,这样可以减轻调用者的负担、使错误不会被忽略、增强程序的可维护性。
    [3]
    (A)忘记保存原始的strDest值,说明答题者逻辑思维不严密。
    [4]
    (A)循环写成while (*strDest++=*strSrc++);,同[1](B)。
    (B)循环写成while (*strSrc!='/0') *strDest++=*strSrc++;,说明答题者对边界条件的检查不力。循环体结束后,strDest字符串的末尾没有正确地加上'/0'。

    2.返回strDest的原始值使函数能够支持链式表达式,增加了函数的“附加值”。同样功能的函数,如果能合理地提高的可用性,自然就更加理想。
    链式表达式的形式如:
        int iLength=strlen(strcpy(strA,strB));
    又如:
        char * strA=strcpy(new char[10],strB);
    返回strSrc的原始值是错误的。其一,源字符串肯定是已知的,返回它没有意义。其二,不能支持形如第二例的表达式。其三,为了保护源字符串,形参用 const限定strSrc所指的内容,把const char *作为char *返回,类型不符,编译报错。

 

 

自己简单写了下程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *strcpy(char *str_dest,const char *str_src)
{
        char *str_destcopy;
        if( (str_dest==NULL) || (str_src==NULL)){
                printf("invalid parameters/n");
                return NULL;
        }

        str_destcopy = str_dest;
        while( (*str_dest++=*str_src++)!='/0');

        return str_destcopy;
}

int main(int argc, char *argv[])
{
        char *str_src="helloworld";
        char *str_dest;
        char *ret;

        str_dest=(char *)malloc(sizeof(char)*10);
        if(NULL!=str_dest){
                memset(str_dest, 0, sizeof(char)*10);
        }
        printf("str_src:%s/n",str_src);
        if( (ret=strcpy(str_dest, str_src)) == NULL)
                printf("strcpy ERROR./n");
        else
                printf("str_dest:%s/n",ret);

}
有两点注意:
1、记得要给字符串指针分配内存:
        str_dest=(char *)malloc(sizeof(char)*10);
        if(NULL!=str_dest){
                memset(str_dest, 0, sizeof(char)*10);
        }

最开始没有分配内存,总是报段错误

另外也采用char str_dest[10];的方式,就不用指针了



2、另外,最开始没有加#include <stdlib.h>和#include <string.h>两句,编译出现的警告:
strcpy.c:23: 警告:隐式声明与内建函数‘malloc’不兼容
strcpy.c:25: 警告:隐式声明与内建函数‘memset’不兼容
后来man了一下这两个函数,发现缺少头文件,包含进来后就没有以上错误了

 

环境:linux系统,gcc4.3.2

<think>嗯,用户问的是“可以对字符串指针直接赋值吗”。之前他已经问过关于字符串指针的关系,现在这个问题可能是在进一步理解指针操作时的疑惑。我需要先回顾之前给他的讲解,看看是否已经覆盖了这部分内容。 首先,用户可能对指针赋值有两种理解:一种是指针本身的赋值,比如让指针指向不同的字符串;另一种是通过指针修改字符串的内容。在之前的回答中,我区分了字符数组和字符指针的不同,特别是在可修改性方面。所以现在需要明确用户的问题具体指向哪一种情况。 接下来,我需要考虑用户可能的背景。他可能正在学习C语言,遇到了指针操作的问题,尤其是字符串处理时常见的错误。比如,直接给指针赋值字符串字面量然后试图修改,或者不清楚指针和数组的区别。这时候,我需要用例子来区分不同情况,并指出哪些是合法的,哪些会导致问题。 用户的问题中提到“直接赋值”,我需要明确两种情况:指针的赋值和内容的修改。例如,char *p = "hello"; 这样的初始化是合法的,属于指针的赋值。而如果用户问的是能否用赋值运算符修改字符串内容,比如*p = 'H',这就要看字符串存储的位置,如果是字面量在常量区,就会导致错误。 另外,用户可能混淆指针和数组的初始化。比如,字符数组在声明时可以初始化,但不能在之后用赋值运算符整体赋值。而指针可以重新指向不同的字符串。这时候需要举例说明,比如char arr[] = "hello"; arr = "world"; 是不允许的,而指针可以p = "world";。 还需要注意动态分配内存的情况,比如使用malloc后,strcpy来复制字符串,而直接赋值指针会导致内存泄漏或者错误。这部分可能也是用户容易犯错的地方。 最后,总结时需要明确回答用户的问题:可以对指针本身直接赋值,但不能通过指针直接修改字符串字面量的内容。同时,要强调const关键字的使用和动态内存管理的注意事项,帮助用户避免常见错误。</think>在C语言中,**对字符串指针的赋值需要分情况讨论**。以下是关键点的系统分析: --- ### 一、指针变量本身的赋值(改变指向) #### ✅ **合法操作** ```c char *str; // 声明指针 str = "Hello"; // 直接赋值为字符串字面量 str = another_str; // 指向其他字符串 ``` - **本质**:改变指针存储的地址值 - **特点**: - 可以无限次重新赋值 - 不涉及原有指向内容的修改 - 适用于指向不同字符串的场景 --- ### 二、通过指针修改字符串内容 #### ⚠️ **风险操作**(取决于字符串存储位置) 1. **指向常量区的字符串(字面量)** ```c char *p = "ABC"; p[0] = 'a'; // 错误!尝试修改只读内存 ``` - **结果**:运行时产生段错误(Segmentation Fault) 2. **指向栈/堆空间的字符串** ```c char arr[] = "Hello"; char *p = arr; p[0] = 'h'; // 合法修改 char *heap_str = malloc(6); strcpy(heap_str, "Test"); heap_str[0] = 't'; // 合法修改 ``` - **特点**:通过指针修改内容的行为是允许的 --- ### 三、数组与指针赋值的核心区别 | 操作类型 | 字符数组示例 | 字符指针示例 | |----------------|--------------------------|--------------------------| | 初始化赋值 | `char arr[] = "Init";` ✅ | `char *p = "Init";` ✅ | | 后续整体赋值 | `arr = "New";` ❌ | `p = "New";` ✅ | | 元素逐个修改 | `arr[0] = 'X';` ✅ | 取决于指向区域(见第二节) | --- ### 四、安全实践指南 #### 1. **只读指针的规范写法** ```c const char *readonly_ptr = "Read-Only"; // 编译器强制保护 // readonly_ptr[0] = 'r'; // 编译报错 ``` #### 2. **动态内存的安全操作** ```c char *dynamic_str = malloc(10); if(dynamic_str != NULL) { strcpy(dynamic_str, "Dynamic"); // 正确的内容赋值方式 // dynamic_str = "NewAddr"; // 危险!会导致内存泄漏 free(dynamic_str); // 必须手动释放 } ``` #### 3. **数组的指针式操作** ```c char arr[20] = "Original"; char *cursor = arr; // 用指针操作数组 *cursor++ = 'N'; // 修改为"Nriginal" ``` --- ### 五、典型错误案例分析 #### ❌ 错误1:混淆指针与数组赋值 ```c char arr[10]; arr = "New"; // 错误!数组名是常量指针 ``` **修正**:使用`strcpy(arr, "New");` #### ❌ 错误2:未初始化的指针赋值 ```c char *wild_ptr; strcpy(wild_ptr, "Crash"); // 野指针操作 ``` **修正**:先分配内存或指向有效地址 #### ❌ 错误3:返回局部数组指针 ```c char* bad_func() { char local[] = "Temp"; return local; // 数组生命周期结束 } ``` **修正**:返回动态分配内存或静态存储的字符串 --- ### 总结回答 1. **指针变量本身可以直接赋值**(改变指向目标) 2. **通过指针修改字符串内容需谨慎**: - 指向常量区时禁止修改 - 指向栈/堆空间时可以修改 3. **数组名不是左值**,不能直接整体赋值 4. 使用`const`修饰符和动态内存管理可显著提升安全性 理解这些规则后,可以避免90%以上的字符串指针操作错误!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值