如何在 c 中正确实现 strcpy?

How to properly implement strcpy in c?

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

#include <stdio.h>
#include <stdlib.h>
int main(){
    char *des = malloc(10);
    for(char *src="abcdef[=10=]";(*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));
}

你正在递增 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
    }

    // '[=10=]' 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 并将源字符串正确复制到位。但是由于您为每个复制的字符递增 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个字节。并且没有必要以 null 终止字符串文字,因为这将由编译器为您完成。

在这个循环中

for(char *src="abcdef[=10=]";(*des++ = *src++););

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

注意显式终止零字符'[=17=]'在字符串文字中是多余的。

循环可以如下所示

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 );

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

复制字符串

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

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

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

复制字符串文字

const char src[] = "abcdef[=11=]"; // string literal here has 2 ending `[=11=]`, 
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);

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

不要在主程序中编程。使用函数.


#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"));
}

But that prints nothing, and no error. What went wrong?

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[=19=]" - 不需要明确的 [=20=]。它在翻译过程中自动附加。使用 "abcdef".

  • 如果分配成功,请始终检查 return 内存管理函数,方法是检查空指针的 returned。

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

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

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

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