[C] Segmentation fault

Le problème exposé dans ce sujet a été résolu.

Bonjour,

D’un point de vue théorique, qu’est-ce qui explique l’erreur de Segmentation Fault dans ce code ?

int main(void)
{
    char* b = "Hello";

    *(b + 1) = 'a';
    return 0;
}

Pour moi, la chaîne "Hello" est un tableau de caractères contenant 6 emplacements (le dernier étant '\0’).

revient à déclarer un pointeur de char, qui pointe vers la chaine statique et constante "hello".

Pourquoi est-ce une chaîne constante et statique ?
Ce n’est pas le cas ici par exemple :

int main(void)
{
    char b[] = "Hello";

    *(b + 1) = 'a';
    return 0;
}

exact, si les 2 déclarations se "ressemblent", les effets sont très différents.

Dans le 1er cas, tu déclares une chaine de caractères constante qui contient "Hello", puis tu déclares un pointeur qui pointe sur cette chaine. Tu ne peux pas modifier le contenu de la chaine de caractères.

Dans le 2nd cas, tu déclares un tableau de taille constante, qui est initialisé avec le contenu "H", "e", "l", "l", "o", "\0". Dans ce cas, tu peux modifier le contenu de ton tableau.

Un petit lien qui explique mieux que moi developpez.com.

… elle se contente de stocker l’adresse d’une chaîne de caractères qui peut se trouver dans un endroit de la mémoire non modifiable par le programme

https://nicolasj.developpez.com/articles/libc/string/

D’accord, mais quelle zone de la mémoire est non-modifiable par le programme ?
Pour moi, il existe 3 zones mémoires :

  • Zone statique, avec les variables globales et statiques
  • Le tas (heap)
  • La pile (stack)

Où se trouve les chaînes de caractères constantes ?

pour info, le code avec le pointeur, compilé avec gcc et l’option -s donne ceci en sortie :

    .file   "main.c"
    .text
    .section    .rodata
.LC0:
    .string "Hello"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    $.LC0, -8(%rbp)
    movq    -8(%rbp), %rax
    addq    $1, %rax
    movb    $97, (%rax)
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (GNU) 7.3.1 20180712 (Red Hat 7.3.1-6)"
    .section    .note.GNU-stack,"",@progbits

On voit bien le .string "Hello" dans la section .rodata, donc accessible en lecture seule.

Quant au code avec tableau, il donne ça avec l’option -s de gcc :

    .file   "main_t.c"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $1819043144, -6(%rbp)
    movw    $111, -2(%rbp)
    movb    $97, -5(%rbp)
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (GNU) 7.3.1 20180712 (Red Hat 7.3.1-6)"
    .section    .note.GNU-stack,"",@progbits

Dans ce cas, on voit bien l’affectation de chaque lettre de la chaine de caractère dans la partie ".text", qui peut donc être modifiée

    movl    $1819043144, -6(%rbp)  // 1819043144 => 0x6c 6c 65 48 => "l l e H"
    movw    $111, -2(%rbp)         // 111        => 0x6F          => "o"
    movb    $97, -5(%rbp)          // 97         => 0x60          => "a"
+1 -0

Salut,

D’accord, mais quelle zone de la mémoire est non-modifiable par le programme ?
Pour moi, il existe 3 zones mémoires :

  • Zone statique, avec les variables globales et statiques
  • Le tas (heap)
  • La pile (stack)

Où se trouve les chaînes de caractères constantes ?

Green

Cela dépend du format de ton exécutable. Pour le format ELF (utilisé sous Linux et BSD) tu as en général ces quatre sections :

  • La section text qui contient le code ;
  • La section data qui contient les variables de classes de stockage statique initialisées ;
  • La section bss qui contient les variables de classes de stockage statique non-initialisées (et qui seront donc initialisées « à zéro ») ;
  • La section rodata qui contient les littéraux comme les chaînes de caractères.

La section rodata, comme son nom l’indique (read only), ne permet pas l’écriture de données, d’où l’erreur de segmentation. Note que la norme n’oblige en rien à ce que les chaînes littérales soient en lecture seule, toutefois elle considère leur modification comme un comportement indéfini (et donc préférablement à proscrire).

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.

ISO/IEC 9899:2017, doc. N2176, 6.4.5, String literals, al. 7, p.51

Sinon, si tu veux creuser un peu le format ELF, tu peux regarder la page de manuel elf(5) et/ou jouer un peu avec la commande readelf. ;)

+3 -0

Merci, et dans ce code-ci :

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int* a = malloc(sizeof(int));
    free(a);
    
    if (a == NULL || a == 0)
        printf("ok\n");
    else
        // a : 0x565557754010
        printf("a : %p\n", (void*)a);
    
    return 0;
}

Que vaut a après le free ?
Une adresse random ? C’est dangereux ?

Salut,

Après le free, a pointe vers la même adresse qu’avant le free. Mais elle ne nous appartient pas, donc oui c’est dangereux.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *a = malloc(sizeof(int));
    if(a)
    {
        printf("%p ", a);
        free(a);
        printf("%p ", a);   
    }
    return 0;
}

Pour que free puisse modifier la valeur de a, il faudrait passer l’adresse de a à free.

+1 -0

OK Merci ! :)

Je vais essayer de toujours remettre les adresses à NULL alors :

#include <stdlib.h>

int main(void)
{
    int* a = malloc(sizeof(int));

    if (a)
    {
        // some friendly code
        
        free(a);
        a = NULL; // On éviter ainsi de toucher à de la mémoire qui nous appartient plus
    }

    return 0;
}
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte