Search code examples
arrayscpointersimplicit-conversionstring-literals

Why am I able to modify char * in this example?


I have problems understanding how char* works.

In the example below, the struses is called by main(). I created a buf to store the const variable because I want to make a modifiable copy of s1, then I just call the sortString().

This version makes sense to me as I understand that char[] can be modified:

#include "common.h"
#include <stdbool.h>
void sortString(char string[50]);

bool struses(const char *s1, const char *s2) 
{

    char buf[50];
    strcpy(buf, s1);  // <===== input = "perpetuity";
    sortString(buf);
    printf("%s\n", buf); // prints "eeipprttuy"
    return true;
}

void sortString(char string[50]) 
{
    char temp;
    int n = strlen(string);
    for (int i = 0; i < n - 1; i++)
    {
        for (int j = i + 1; j < n; j++)
        {
            if (string[i] > string[j])
            {
                temp = string[i];
                string[i] = string[j];
                string[j] = temp;
            }
        }
    }
}

However, in this version I deliberately changed the type to char* which is supposed to be read-only. Why do I still get the same result?

#include "common.h"
#include <stdbool.h>
void sortString(char *string);

bool struses(const char *s1, const char *s2)
{

    char buf[50];
    strcpy(buf, s1); 
    sortString(buf);
    printf("%s\n", buf);
    return true;
}

void sortString(char *string)  // <==== changed the type
{
    char temp;
    int n = strlen(string);
    for (int i = 0; i < n - 1; i++)
    {
        for (int j = i + 1; j < n; j++)
        {
            if (string[i] > string[j])
            {
                temp = string[i];
                string[i] = string[j];
                string[j] = temp;
            }
        }
    }
}

This is why I think char * is read only. I get a bus error after trying to to modify read[0]:

char * read = "Hello";
read[0]='B';// <=== Bus error
printf("%s\n", read); 

Solution

  • The compiler adjusts the type of the parameter having an array type of this function declaration

    void sortString(char string[50]);
    

    to pointer to the element type

    void sortString(char *string);
    

    So for example these function declarations are equivalent and declare the same one function

    void sortString(char string[100]);
    void sortString(char string[50]);
    void sortString(char string[]);
    void sortString(char *string);
    

    Within this function

    void sortString(char *string)
    

    there is used the character array buf that stores the copy of the passed array (or of the passed string literal through a pointer to it)

    char buf[50];
    strcpy(buf, s1);
    sortString(buf);
    

    So there is no problem. s1 can be a pointer to a string literal. But the content of the string literal is copied in the character array buf that is being changed

    As for this code snippet

    char * read = "Hello";
    read[0]='B';
    printf("%s\n", read); <=== still prints "Hello"
    

    then it has undefined behavior because you may not change a string literal.

    From the C Standard (6.4.5 String literals)

    7 It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.

    Pay attention to that in C++ opposite to C string literals have types of constant character arrays. It is advisable also in C to declare pointers to string literals with the qualifier const to avoid undefined behavior as for example

    const char * read = "Hello";
    

    By the way the function sortString has redundant swappings of elements in the passed string. It is better to declare and define it the following way

    // Selection sort
    char * sortString( char *s ) 
    {
        for ( size_t i = 0, n = strlen( s ); i != n; i++ )
        {
            size_t min_i = i;
    
            for ( size_t j = i + 1; j != n; j++ )
            {
                if ( s[j] < s[min_i] )
                {
                    min_i = j;
                }
            }
    
            if ( i != min_i )
            {
                char c = s[i];
                s[i] = s[min_i];
                s[min_i] = c;
            }
        }
    
        return s;
    }