Is this a legal C strdup function?

166 Views Asked by At

I tried to make my own strdup function to practice my C skills, but I'm not sure if it's legal or not.

the function is down below:

char*
strdup(char* str) {
    char* dup_str;
    int len;
    for (len = 0; *str != '\0'; ++len) {
        dup_str = str;
        dup_str++;
        str++;
    }
    
    dup_str -= len;
    return dup_str;
}

I have tested it a bit, but I'm still not sure if it is legal. It may be completely incorrect, so I'm surprised that it works the way it does at the moment.

3

There are 3 best solutions below

15
ikegami On BEST ANSWER

Ignoring the undefined behaviour that occurs for strings larger than INT_MAX chars long, your code is equivalent to the following:

char *my_strdup( char *old ) {  // XXX Shouldn't require a modifiable string.
   if ( *old ) {
      return old;               // XXX Not a pointer to a new string.
   } else {
      char* indeterminate;
      return indeterminate;     // XXX Indeterminate value.
   }
}

This is completely wrong. For starters, strdup's main purpose is to allocate a new string, yet your function allocates no memory.

strdup can be summarized into four steps:

  1. Determine the amount of memory to allocate.
  2. Allocate memory for the new string.
  3. Copy the old string into the newly-allocated buffer.
  4. Return the pointer to the newly-allocated buffer.

Fixed: (Added after OP made attempt to fix in the comments.)

char *my_strdup( const char* old ) {
   size_t n = strlen( old ) + 1;
   char *new = malloc( n );
   if ( !new ) {
      errno = ENOMEM;
      return NULL;
   }

   return memcpy( new, old, n );
}

We can also replace the other string functions.

size_t my_strlen( const char *str ) {
   size_t len = 0;
   while ( *( str++ ) )
      ++len;

   return len;
}
void *my_memcpy( void *restrict dst, const void *restrict src, size_t count ) {
   void *rv = dst;

   while ( count-- )
      *( dst++ ) = *( src++ );

   return rv;
}
1
Vlad from Moscow On

Before writing such a function yourself you should read the description of the POSIX function strdup that is also included in the C23 Standard.

The function is declared like

char *strdup(const char *s);

As you can see its single parameter is declared with the qualifier const because the passed source string is not changed within the function. So your function is already wrong because its declaration (its interface) only confuses users.

After this for loop

for (len = 0; *str != '\0'; ++len) {
    dup_str = str;
    dup_str++;
    str++;
}

the pointer dup_ptr points to the last character of the source string that is to the terminating zero character '\0' provided that the length of the source string is not greater than the value of the macro INT_MAX. Otherwise the function invokes undefined behavior.

Moreover if the passed string is an empty string then the pointer dup_str will stay uninitialized and the uninitialized pointer with an indeterminate value will be returned from the function.

After this statement

dup_str -= len;

the pointer dup_str is set to the address of the first character of the source string and this address is returned from the function.

In fact your function looks like

char* strdup(char* str) {
    char* dup_str = str;
    
    return dup_str;
}

privided that theh source string is not empty.

As you can see neither duplicate string is created. There is no great sense to write a whole function to assign one variable to another variable.

You need to reserve a memory for the duplicate string.

Let's read what is written in the C23 Standard (7.26.2.6 The strdup function):

2 The strdup function creates a copy of the string pointed to by s in a space allocated as if by a call to malloc.

3 The strdup function returns a pointer to the first character of the duplicate string. The returned pointer can be passed to free. If no space can be allocated the strdup function returns a null pointer.

Following this description the function can be declared and defined the following way

char * strdup( const char *s )
{
    size_t n = strlen( s );
    char *dup_str = malloc( n + 1 );

    if ( dup_str != NULL ) strcpy( dup_str, s );

    return dup_str;
} 

Instead of the call of strcpy you may use a call of memcpy like

memcpy( dup_str, s, n + 1 );

Without using the standard C string functions strlen and strcpy the function can be defined the following way

char * strdup( const char *s )
{
    size_t n = 0;

    while ( s[n] != '\0' ) ++n;

    char *dup_str = malloc( n + 1 );

    if ( dup_str != NULL )
    {
        for ( char *tmp = dup_str; ( *tmp++ = *s++ ) != '\0'; );
    }

    return dup_str;
} 

Pay attention to that the function follows the C Standard convention for string functions and does not check whether a passed pointer is a null pointer. It is the user responsibility to pass a valid pointer.

Also you need to remember that when the created duplicate string is not needed any more you need to free the allocated memory for it using function free(). For example

int main( void )
{
    const char *source = "Hello World!";

    char *copy = strdup( source );

    if ( copy != NULL ) puts( copy );


    free( copy );
}
0
Patrick Schlüter On

You do not allocate a new string and have some other minor errors (non const parameter, uninitialized pointer on empty string).

Here an implementation following the behaviour described by man on Linux. Note the setting of errno on allocation error.

 char *strdup(const char *str)
 {
     size_t len = strlen(str)+1;
     char *r = malloc(len); 
     if(r)
       return memcpy(r, str, len);
     else 
       errno = ENOMEM;
     return NULL;
 }

variation

 char *strdup(const char *str)
 {
     size_t len = strlen(str)+1;
     char *r = malloc(len); 
     return r ? memcpy(r, str, len) : (errno=ENOMEM, NULL);
 }