C pointer concept segmentation fault

138 Views Asked by At

Please take a look of snippet: I am not able to understand why code is giving segmentation fault and garbage characters.

#include <stdio.h>

int main()
{
    char *str[1];
    str[0] = "apple";

    char **ptr = str;
    char *p = str;

    printf("%d\n", ptr);  //---> 1956347328
    printf("%d\n", p);      //---> 1956347328

    printf("%s \n", *ptr, ); // ---> prints apple
    printf("%s\n", *p);    // ---> error segmentation fault
    printf("%s\n", p);    // ---> Garbage characters    
}
4

There are 4 best solutions below

0
chqrlie On BEST ANSWER

Here is a detailed explanation of the posted code behavior and problems:

  • char *str[1]; defines an array of pointers to char with a single element.

  • str[0] = "apple"; might trigger a warning as you store a pointer to a string literal, which must not be modified, into an element of the array which contains pointer to modifiable data. As long as you do not use this pointer to actually try and modify the string, you are safe.

  • char **ptr = str; defines another object, a pointer to a pointer to char, intialized to point to the array str. No problem with that, but you can only access the element at ptr[0].

  • char *p = str; defined another object, a pointer to char initialized to point to the array str. This is a type mismatch as the type of str is neither array of char nor pointer to char, hence the compiler should issue a warning. Yet as long as you only use this pointer to access the bytes that compose this array, you are safe. The array contains a single pointer (that points to the "apple" string), which on your 64-bit machine is composed of 8 bytes.

  • printf("%d\n", ptr) has undefined behavior because printf expects an argument of type int for the %d format, and you pass a char ** value. On your architecture, integers and pointers seem to be passed in the same register or stack area, so printf outputs an integer that is a part (the low 32 bits) of the pointer, 1956347328. You should instead use printf("%p\n", (void *)ptr);

  • printf("%d\n", p) same remark as above, the output is the same because both pointers point to the same address, but as they have different types, dereferencing them produces different things.

  • printf("%s \n", *ptr, ) is a syntax error, but without the extra , printf receives *ptr, which is the first element of the array str, ie: the char * pointer to the string "apple", which is what it expects for %s. printf outputs the string apple, a space and a newline.

  • printf("%s\n", *p): printf receives *p, which is the char at the beginning of the str array. This byte is the first byte of the pointer to the string, which has one of 256 possible values for such a pointer, in the range CHAR_MIN to CHAR_MAX (0..255 or -128..127 depending on the signedness of type char). printf expects a pointer to char as the argument for the %s conversion. Passing the char value has undefined behavior, and the value used by printf in your case is an invalid pointer: dereferencing it causes a segmentation fault.

  • printf("%s\n", p): the pointer p is passed to printf for the %s conversion, which is fine, but printf reads and outputs the bytes it points to until it reads a null byte. These bytes are not those of the string apple, they are the bytes that compose the pointer stored in str[0], which look like garbage values when output as characters. If printf did not find a null byte as it reads them, it would invoke undefined behavior whren reading past the end of the array and possibly cause a segmentation fault too.

Consider this modified version:

#include <stdio.h>

void dump(const char *s, void *addr, size_t len) {
    printf("%s is at address %p, contains:", s, addr);
    unsigned char *p = addr;
    for (size_t i = 0; i < len; i++)
        printf(" %02x", p[i]);
    printf("\n");
}

int main(void) {
    char *str[1] = { (char *)"apple" };
    char **ptr = str;
    char *p = (char *)str;

    dump("str ", &str, sizeof(str));
    dump("ptr ", &ptr, sizeof(ptr));
    dump("p   ", &p, sizeof(p));

    dump("*ptr", *ptr, sizeof("apple"));

    printf("*ptr as string: %s\n", *ptr);
    printf("*p   as byte  : %02x\n", *p);

    return 0;
}

I get this output on my macbook:

str  is at address 0x30cb5f278, contains: 6b ef 3d 04 01 00 00 00
ptr  is at address 0x30cb5f270, contains: 78 f2 b5 0c 03 00 00 00
p    is at address 0x30cb5f268, contains: 78 f2 b5 0c 03 00 00 00
*ptr is at address 0x1043def6b, contains: 61 70 70 6c 65 00
*ptr as string: apple
*p   as byte  : 6b

If I run the same executable again, I get different output because of address space randomisation, a technique used to make it more difficult for hackers to exploit software vulnerabilities.

The output shows that:

  • the 3 objects p, ptr and str have a size of 8 bytes.

  • they are located in memory in adjacent places (in stack, below the main arguments).

  • p and ptr point to the area occupied by str: the address 0x3085b8280 is stored in little endian order, ie: from the least significant byte to the most significant byte. This looks like reverse order but the order in which to store the bytes of a pointer (or integer) is only a matter of convention, just like the order of parts of speech in a phrase vary from one language to another.

  • the string "apple" which contains the bytes 61 70 70 6c 65 00 is located at address 0x1023dff58 (a constant data segment).

  • *p has the value 6b which is a k, but, on my laptop, this value varies from one run to another as explained here above.

4
Allan Wind On

char *p=str; is an incompatible pointer type initialization as str is a char *[1]. Perhaps you mean char *p = *str?

printf("%d\n",ptr) is not the way you print a pointer. On my system an int (for %d) is 4 bytes and a pointer is 8 bytes. Instead you should do printf("%p\n", (void *) ptr).

printf("%s \n",*ptr,); is a syntax error due to the missing argument.

I think this is what you wanted:

#include <stdio.h>

int main(void) {
    char *str[1];
    str[0] = "apple";

    char **ptr = str;
    char *p=*str;

    printf("%p\n", (void *) ptr);
    printf("%p\n", (void *) p);

    printf("%s \n",*ptr);
    printf("%s\n", p); 
    printf("%c\n", *p);

}

and the output is:

0x7fffcb2bb178
0x563522724004
apple 
apple
a
0
Vlad from Moscow On

Arrays used in expressions with rare exceptions are implicitly converted to pointers to their first elements.

So the array str declared like

char * str[1];

used as an initializer in these declarations

char **ptr = str;
char *p=str;

is converted implicitly to a pointer of the type char **.

For the second declaration the compiler should issue a message that there are used incompatible pointer types: char * and char **.

These calls of printf

printf("%d\n",ptr);  //---> 1956347328
printf("%d\n",p);      //---> 1956347328

are invalid due to using the incorrect conversion specifier d designed to output objects of the type int with objects of pointer types. Instead you shall write

printf("%p\n", ( void * )ptr);    //---> 1956347328
printf("%p\n", ( void * )p);      //---> 1956347328

Nevertheless as it is seen from the output the both pointers contain the address of the first element of the array str. So dereferencing the pointer of the type char ** you will get the first element of the array str of the type char * and thus can output the string pointed to by the element:

printf("%s \n",*ptr,);

In this call of printf

printf("%s\n",*p);  

the expression *p has type char. As a result only one byte of the above shown address is read then promoted to the type int and is used as a valid address. As a result a segmentation fault occurs.

In this call of printf

printf("%s\n",p);

instead of using the value of the first element (pointer) of the array str as in this call

printf("%s \n",*ptr,);

there is used the address of the first element and the stored pointer value of the first element is outputted as a string.

Instead you could write for example

printf("%s\n", *( char ** )p);

making the call similar to the call

printf("%s \n",*ptr,);

that correctly outputs the string pointed to by the first element of the array str.

0
ezhil .n On

You're assigning a pointer to an array of strings (str[0] = "apple";).However str is declared as an array of pointers to characters. you should use char str[] = "apple"; if you intend to use it as an array of characters.

char *p = str; is trying to assign a char **. I think the following code will help you.

#include <stdio.h>

int main() {
    char str[] = "apple";  // Correctly initialize str as an array of characters

    char *ptr = str;       // Use char * instead of char ** to assign the pointer to str

    printf("%p\n", (void *)ptr);  // Use %p to print pointers 
    printf("%p\n", (void *)str); //Print the address of the str(same as above line)

    printf("%s\n", ptr);   // Prints "apple"
    printf("%s\n",str);   //Prints "apple"
    printf("%c\n", *ptr);  // Prints 'a', accessing individual character
    
    return 0;
}

output:

0x7ffc41ae4a02
0x7ffc41ae4a02
apple
apple
a