The key point is that arguments are passed by value in C.
Hence any change of a formal argument is never seen by the caller function.
At last, your my_strcpy is not behaving like the standard strcpy (which does not allocate memory using malloc). And your use of malloc is wrong, since sizeof(src) is always sizeof(char*) (i.e. 8 on Linux/x86-64).
Here is a better version:
void my_strcpy( char *dest, const char* src ) {
while ( *src != '\0' ) {
*dest = *src;
src++;
dest++;
}
*dest = '\0'; // zero terminate the string
}
Notice that strcpy (and my_strcpy) is intrinsically dangerous because you may have buffer overflow (so use strncpy more often). A typical use case would be
char buf[80];
my_strcpy(buf, "hello I am here");
so we have local memory (on the call stack typically) and we copy into it. If the literal string was very long (e.g. more than 80 characters) we would have a buffer overflow.
BTW, your test while(src != '\0') was very wrong. A pointer is not the same the pointed value. Your wrong test would typically loop a billion times on a 32 bits machine and would crash before reaching the terminating condition src == NULL
Also in practice strcpy is often known by optimizing compilers like GCC which may even generate a loop, not a function call to a standard function, when compiling it.
Several of your mistakes would probably have been spotted by your compiler (e.g. GCC) if you enable all warnings in your compiler (which you always should), so compile e.g. with gcc -Wall -Wextra -g (to get all warnings, some more, and debug information) then use the debugger gdb to run your program step by step.
In practice, when possible, have functions returning pointers, like
char* my_almost_strdup(const char*src) {
size_t ln = strlen(src);
char* newptr = malloc(ln+1);
if (!newptr) { perror("my_strdup"); exit(EXIT_FAILURE); };
strcpy (newptr, src);
return newptr;
}
this is a bit like the standard strdup except that I am catching the out-of-memory error case (when malloc fails). It is common to just show some error message and exit in that failing case, but you should always handle malloc failure
Read more about C dynamic memory management and document your memory conventions (when having a function allocating memory in heap thru malloc or calloc etc... you need to document who is in charge of freeing it and how).
Be afraid of undefined behavior and of memory leaks. Use valgrind if available.
Read a bit more about garbage collection (it is a very useful terminology and concept). You might want to code some C programs using Boehm conservative garbage collector (but using Boehm's GC is a whole program decision).
In your main
my_strcpy( &test, &s ); // VERY BAD
is many times wrong (undefined behavior because of buffer overflow). You should pass an address of a memory zone able to contain the string (including its terminating zero byte), and the second argument does not have the right type (&s is a char** not a const char*).