Search code examples
cpointersinitializationscalarbraced-init-list

assigning string literal vs array to char


I have a char pointer with string literal like this char *d="abc"; and I am incrementing it like

*(d+1)

I get something like b if I do printf("%c",*(d+1))

But when I have these lines

char *c={'a','b','c'}
printf("%c\n",*(c+1)); /// CAUSES SegFault

Above line throwing exception. when I try to do backtrace and print *(c+1) with gdb it says $1 = 0x61 <error: Cannot access memory at address 0x61>

So my question is why this does not work compare to when I assign string literal to char pointer

same happens when I assign array of int to int pointer and increment it this way


Solution

  • Major thanks to @nielsen for pointing this out, it all became clear after their comment.

    First of all, let's try a similar program that won't segfault:

    #include <stdio.h>
    
    int main()
    {
        char *a = {'a', 'b', 'c'};
        printf("%p\n", (void *) a);
    }
    

    For me, this outputs: 0x61. That should ring a bell, it's the same address that GDB gave.

    More important, however, are the warnings I am getting:

    main.c:5:16: warning: initialization makes pointer from integer without a cast [-Wint-conversion]                                                           
         char *a = {'a', 'b', 'c'};                                                                                                                             
                    ^~~                                                                                                                                         
    main.c:5:16: note: (near initialization for ‘a’)                                                                                                            
    main.c:5:21: warning: excess elements in scalar initializer                                                                                                 
         char *a = {'a', 'b', 'c'};
    
    main.c:5:16: warning: initialization makes pointer from integer without a cast [-Wint-conversion]                                                           
         char *a = {'a', 'b', 'c'};                                                                                                                             
                    ^~~                                                                                                                                         
    main.c:5:16: note: (near initialization for ‘a’)                                                                                                            
    main.c:5:21: warning: excess elements in scalar initializer                                                                                                 
         char *a = {'a', 'b', 'c'};
    
    main.c:5:21: warning: excess elements in scalar initializer                                                                                                 
         char *a = {'a', 'b', 'c'};                                                                                                                             
                         ^~~                                                                                                                                    
    main.c:5:21: note: (near initialization for ‘a’)                                                                                                            
    main.c:5:26: warning: excess elements in scalar initializer                                                                                                 
         char *a = {'a', 'b', 'c'};                                                                                                                             
                              ^~~                                                                                                                               
    main.c:5:26: note: (near initialization for ‘a’)
    

    initialization makes pointer from integer without a cast [-Wint-conversion] was already pointed out in the comments. However, with another warning, this becomes clear:

    main.c:5:21: warning: excess elements in scalar initializer                                                                                                 
         char *a = {'a', 'b', 'c'};                                                                                                                             
                         ^~~                                                                                                                                    
    main.c:5:21: note: (near initialization for ‘a’)                                                                                                            
    main.c:5:26: warning: excess elements in scalar initializer                                                                                                 
         char *a = {'a', 'b', 'c'};
    

    Basically, this doesn't do what you think it does. At all. the {} is a "scalar" initializer. From https://en.cppreference.com/w/c/language/type, here's an excerpt:

    scalar types: arithmetic types and pointer types

    A pointer happens to be a scalar type because it can only hold 1 value, which is an address. So the compiler will only use 'a' to initialize c since c can only hold 1 value, and ignores everything else (because again, scalar). What's the ASCII value of 'a' in hex? 61, the exact same number as the address GDB pointed out. Hopefully, you get what's going on now:

    • When the compiler sees char *c = {'a', 'b', 'c'};, it treats the aggregate initializer as a scalar initializer because c is a scalar variable, so only takes 'a' and tells you off for putting 2 extra characters.

    • 'a', an int literal, is implicitly converted to char * and it becomes an address. The compiler also warns you about this.

    • You try to print *(c + 1), but since that is an invalid address/you're not allowed to touch that address, a segfault occurs.


    What I think you actually want to do is treat c as an array. To do this, you can either change c's type into an array:

    char c[] = {'a', 'b', 'c'};
    

    Or keep c as a char * and use a compound literal:

    char *c = (char []) {'a', 'b', 'c'};
    

    However, char *c = {'a', 'b', 'c'}; is not valid C as a brace-enclosed scalar initializer is only allowed to hold 1 expression. Vlad's answer gives the specific quote from the standard that proves this. Compiling this code with -pedantic-errors will replace all the warnings mentioned here with errors.