【问题标题】:How to properly implement strcpy in c?如何在c中正确实现strcpy?
【发布时间】:2020-10-11 09:15:52
【问题描述】:

据此: strcpy vs strdup, strcpy 可以用一个循环来实现,他们使用了这个while(*ptr2++ = *ptr1++)。我尝试过类似的操作:

#include <stdio.h>
#include <stdlib.h>
int main(){
    char *des = malloc(10);
    for(char *src="abcdef\0";(*des++ = *src++););
    printf("%s\n",des);
}

但这什么也没打印,也没有错误。出了什么问题?

非常感谢您的回答,我玩了一点,并决定如何最好地设计循环以查看复制是如何逐字节进行的。这似乎是最好的:

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

int main(){
    char *des = malloc(7);
    for(char *src="abcdef", *p=des; (*p++=*src++); printf("%s\n",des));
}

【问题讨论】:

  • 从中学到一些东西:将所有这些操作不必要地塞进一行代码使您无法理解实际发生的情况。看到你可以在一行中塞进多少代码并没有什么收获。
  • @AndrewHenle 所以你曾经以一种不常见的方式看到for loop 的用法并称之为填鸭式? for loop 是所有编程中非常基本的概念,c 具有以任何你喜欢的方式使用它的优势。我以与您习惯的 for(int i =0;i&lt;10;i++) 不同的方式使用它,但这只是我的偏好。
  • @milanHrabos 作为一个非常初学者,你的意见有点太强烈了。您的代码难以阅读,有经验的程序员更喜欢写更多的行而不是“压缩”到这种怪物中。
  • @milanHrabos 你不得不问这个问题,因为那一行代码太复杂了,你无法弄清楚。故事结局。 “代码简洁”是货物狂热的 BS,它导致了一些问题,使您无法理解您的代码在做什么。有一整套经过同行评审的研究证明人类的大脑只能同时跟踪少数几个单独的状态 - 这个数字通常从 3 到 6 不等或 7 件事。 (*des++ = *src++) 循环中的 (*des++ = *src++) 部分同时发生 6 件事。是的 - 你塞得太多了。
  • 你可以从中学习,或者你可以继续用头撞砖墙,试图理解一行代码太多。您的选择。

标签: c string pointers strcpy


【解决方案1】:

你正在递增des,所以在循环结束时它会自然地指向字符串的末尾,打印它等于undefined behavior,你必须把它带回到des的开头。

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

int main(){
    int count = 0;
    char *des = malloc(10);

    if(des == NULL){
       return EXIT_FAILURE; //or otherwise handle the error
    }

    // '\0' is already added by the compiler so you don't need to do it yourself
    for(char *src="abcdef";(*des++ = *src++);){
        count++; //count the number of increments
    }
    des -= count + 1; //bring it back to the beginning
    printf("%s\n",des);
    free(dest); //to free the allocated memory when you're done with it
    return EXIT_SUCCESS;
}

或者创建一个指向 des 开头的指针并打印出来。

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

int main(){
 
    char *des = malloc(10);

    if(des == NULL){
       return EXIT_FAILURE; //or otherwise handle the error
    }

    char *ptr = des;
    for(char *src="abcdef";(*des++ = *src++);){} //using {} instead of ;, it's clearer

    printf("%s\n",ptr);
    free(ptr) // or free(dest); to free the allocated memory when you're done with it
    return EXIT_SUCCESS;

}

【讨论】:

  • 这里(*des++ = *src++),确实必须满足,但它只是满足——只要——值(地址)不是0。但是,如果您没有将\0 放在*src 的末尾,那么它将继续(读取)无穷大(或崩溃)。但是它不会崩溃,所以它会为你编译吗?他会在字符串的末尾为你添加 '\0' 空字节吗?
  • 但他们不是。我认为它只是来自某些版本的编译器,您不知道是什么版本吗?
  • @milanHrabos,是的,src 由编译器终止,即它在字符串末尾放置一个 '\0',所有编译器都这样做,这是强制性的。
【解决方案2】:

您分配目标缓冲区des 并将源字符串正确复制到位。但是由于您为复制的每个字符递增des,因此您已将des 从字符串的开头移动到结尾。当您打印结果时,您正在打印最后一个字节,即 nil 终止,它是空的。

相反,您需要保留一个指向字符串开头的指针,以及一个指向您复制的每个字符的指针。

与原始来源的最小变化是:

#include <stdio.h>
#include <stdlib.h>
int main(){
    char *des = malloc(10);
    char *p = des;
    for(char *src="abcdef";(*p++ = *src++););
    printf("%s\n",des);
}

所以p 是指向下一个目标字符的指针,它沿着字符串移动。但您打印的最后一个字符串是des,从分配开始。

当然,您还应该为des 分配价值strlen(src)+1 的字节。并且没有必要对字符串文字进行空终止,因为编译器会为您完成。

【讨论】:

  • 是否可以分配 3 个变量?撒谎char *p = *des = malloc(10)?
  • "与原始来源的最小变化是" - 最小的变化并不总是最好的。
  • 另外你说des 会指向错误的 NUL 字符。它指向一个元素过去NUL
  • "当然,你也应该为des分配strlen(src)+1的字节。" - 虽然这带来了内存优势,但没有必要这样做. 10 chars 也将保存字符串。此外,OP 还需要以这种方式将 char *src="abcdef" 放在 char *des = malloc(10); 之前。你忘了提。
  • @milanHrabos "是否可以分配 3 个变量?比如char *p = *des = malloc(10)?" - 是的,但不是这样。 F.e. char* des, *p; p = des = malloc(sizeof(char) * 10);
【解决方案3】:

在这个循环中

for(char *src="abcdef\0";(*des++ = *src++););

目标指针des 正在更改。所以在循环之后它不会指向复制字符串的开头。

请注意,显式终止零字符'\0' 在字符串文字中是多余的。

循环可以如下所示

for ( char *src = "abcdef", *p = des; (*p++ = *src++););

然后在循环之后

puts( des );

free( des );

你可以编写一个类似于strcpy的单独函数

char * my_strcpy( char *des, const char *src )
{
    for ( char *p = des; ( *p++ = *src++ ); );

    return des;
}

然后这样称呼它

puts( my_strcpy( des, "abcdef" ) )'
free( des );

【讨论】:

  • 谢谢,很好的答案,虽然不需要free 指向程序末尾的指针,因为这是操作系统的工作
  • @milanHrabos 好吧,他的意思很好,如果您的示例将成为更大计划的一部分。事实上,现实世界中没有任何程序可以将字符串复制到动态内存并打印出来。弗拉德考虑了这一点,并认为它是答案的一部分。
【解决方案4】:

printf("%s\n",des);未定义的行为 (UB),因为它试图从写入分配内存的字符串末尾开始打印。

复制字符串

保存原始指针,检查并在完成后释放。

const char *src = "abcdef\0"; // string literal here has 2 ending `\0`, 
char *dest = malloc(strlen(src) + 1);  // 7

char *d = dest;
while (*d++ = *src++);
printf("%s\n", dest);
free(dest);

复制字符串文字

const char src[] = "abcdef\0"; // string literal here has 2 ending `\0`, 
char *dest = malloc(sizeof src);  // 8

for (size_t i = 0; i<sizeof src; i++) {
  dest[i] = src[i];
}

printf("%s\n", dest);
free(dest);

【讨论】:

  • 如果将数组第一个元素的地址分配给指针p,也可以在第二个示例中使用while (*d++ = *p++);
  • @RobertSsupportsMonicaCellio 在“复制字符串文字”中,while (*d++ = *p++); 在文字有 2 个 空字符 时过早停止 1。
  • 是的,但是 OP 想要字符串,而不是另一个多余的空字符。我喜欢迂腐,但没有必要转移他的问题。
【解决方案5】:

你只需要记住原来分配的指针。

不要在 main 中编程。使用函数


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

size_t strSpaceNeedeed(const char *str)
{
    const char *wrk = str;
    while(*wrk++);
    return wrk - str;
}

char *mystrdup(const char *str)
{
    char *wrk;
    char *dest = malloc(strSpaceNeedeed(str));

    if(dest)
    {
        for(wrk = dest; *wrk++ = *str++;);
    }   
    return dest;
}

int main(){
    printf("%s\n", mystrdup("asdfgfd"));
}

甚至更好

size_t strSpaceNeedeed(const char *str)
{
    const char *wrk = str;
    while(*wrk++);
    return wrk - str;
}

char *mystrcpy(char *dest, const char *src)
{
    char *wrk = dest;
    while((*wrk++ = *src++)) ;
    return dest;
}

char *mystrdup(const char *str)
{
    char *wrk;
    char *dest = malloc(strSpaceNeedeed(str));

    if(dest)
    {
        mystrcpy(dest, str);
    }   
    return dest;
}

int main(){
    printf("%s\n", mystrdup("asdfgfd"));
}

【讨论】:

    【解决方案6】:

    但这什么也没打印,也没有错误。出了什么问题?

    des 在执行(*des++ = *src++) 之后不再指向字符串的开头。事实上,des 指向的元素是 NUL 字符之后的一个元素,该字符随后终止字符串。

    因此,如果您想使用printf("%s\n",des) 打印字符串,它会调用未定义的行为。

    您需要将“开始”指针(指向已分配内存块的第一个char 对象)的地址值存储到临时“持有人”指针中。有多种可能的方法。

    #include <stdio.h>
    #include <stdlib.h>
    
    int main (void) {
        char *des = malloc(sizeof(char) * 10);
        if (!des)
        {
            fputs("Error at allocation!", stderr);
            return 1;
        }
    
        char *tmp = des;
    
        for (const char *src = "abcdef"; (*des++ = *src++) ; );
        des = temp;
    
        printf("%s\n",des);
    
        free(des);
    }
    

    替代方案:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main (void) {
        char *des = malloc(sizeof(char) * 10);
        if (!des)
        {
            fputs("Error at allocation!", stderr);
            return 1;
        }
    
        char *tmp = des;
    
        for (const char *src = "abcdef"; (*des++ = *src++) ; );
    
        printf("%s\n", tmp);
    
        free(tmp);
    }
    

    #include <stdio.h>
    #include <stdlib.h>
    
    int main (void) {
        char *des = malloc(sizeof(char) * 10);
        if (!des)
        {
            fputs("Error at allocation!", stderr);
            return 1;
        }
    
        char *tmp = des;
    
        for (const char *src = "abcdef"; (*tmp++ = *src++) ; );
    
        printf("%s\n", des);
    
        free(des);
    }
    

    旁注:

    • "abcdef\0" - 不需要显式的\0。它会在翻译过程中自动附加。使用"abcdef"

    • 如果分配成功,通过检查返回的空指针来检查内存管理函数的返回。

    • 通过const 限定指向字符串字面量的指针以避免无意的写入尝试。

    • 在调用 malloc 时使用 sizeof(char) * 10 而不是普通的 10。如果类型发生变化,这可以确保写入大小。

    • int main (void) 而不是int main (void)。第一个符合标准,第二个不符合。

    • 始终free() 动态分配内存,因为您不再需要分配的内存。在上面的示例中,这将是多余的,但是如果您的程序变得更大并且示例是部分关注的,您应该立即free() 不需要的内存。

    【讨论】:

    • OP 至少将接受的答案更改为更好的答案。不幸的是,不是我的:/,好吧,我们不能拥有一切。
    • @anastaciu 在生活中会发生疯狂的事情。我有时会看到对旧问题的公认答案,这些答案甚至没有解决问题。
    • 是的,这是有道理的,就是这样,基本上 OP 可以接受任何内容,即使是简单的错误答案,这就是它的本质。我们只能依靠负责任的用户对其投反对票,但如果没有人关注,这总是会失败。
    • @anastaciu 一个例子:Here - 这是关于答案放错位置的评论的直接链接。
    • @anastaciu 答案实际上解决了问题标题,但不是问题。
    猜你喜欢
    • 2013-01-06
    • 2013-10-18
    • 1970-01-01
    • 2021-08-12
    • 2011-02-08
    • 2015-03-19
    • 2012-10-07
    • 2015-06-24
    • 1970-01-01
    相关资源
    最近更新 更多