assigning string literal vs array to char

452 Views Asked by At

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

2

There are 2 best solutions below

5
On BEST ANSWER

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.

0
On

This declaration where you forgot to place a semicolon

char *c={'a','b','c'};
                   ^^^^

is not a valid construction in C. You may not initialize a scalar object with a braced list that contains more than one initializer.

From the C Standard (6.7.9 Initialization)

11 The initializer for a scalar shall be a single expression, optionally enclosed in braces. The initial value of the object is that of the expression (after conversion); the same type constraints and conversions as for simple assignment apply, taking the type of the scalar to be the unqualified version of its declared type.

So the compiler shall issue an error message and in fact there is nothing to discuss because you have a program that shall not be successfully compiled.

You could write for example

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

In this case an expression with the comma operator will be used as an initializer expression. This initialization is equivalent to

char *c = { 'c'};

Thus the pointer c is initialized by the internal code of the character 'c'. For example if there is used the ASCII table then the above initialization is equivalent to

char *c = 99;

Again the compiler should issue a message that you are trying to initialize a pointer with an integer.

As the value 99 used as an address does not point to a valid object in your program then this statement

printf("%c\n",*(c+1));

invokes undefined behavior.

Or you could use for example a compound literal to initialize the pointer c as it is shown in the demonstrative program below

#include <stdio.h>

int main(void) 
{
    char *c = ( char [] ){ 'a', 'b', 'c' };
    printf( "%c\n", *(c+1) );
    
    return 0;
}

In this case you will get the expected result. The only difference with pointers that point to string literals that is that point to strings is that in the program the pointer c does not point to a string. But you could initialize it like

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

and it will point to a string.