Strona 1 z 1

[+] C, naruszenie ochrony pamięci przy niektórych opcjach napisanego programu

: 27 grudnia 2010, 21:39
autor: bigben
Witam.

W ramach nauki C postanowiłem napisać grę kółko i krzyżyk. Sama gra już chodzi ale problem pojawia się gdy wpiszę zbyt duży numer pola. Oto kod źródłowy:

Kod: Zaznacz cały

#include <stdio.h>
#include <stdbool.h>

void rysowanie(char plansza[3][3]); //rysowanie planszy
void tura(char gracz, char plansza[3][3], int wybor, bool zajete[9]); //obsluga tury gracza
char wynik(char plansza[3][3]); //sprawdzenie czy ktos wygral

//zmienna okresla ktory gracz wygral
char wygrana = ' ';

int main()
{
    //deklarowanie poczatkowego stanu planszy

    char plansza[3][3] = {
    { ' ', ' ', ' ' },
    { ' ', ' ', ' ' },
    { ' ', ' ', ' ' }
    };

    //zmienna okreslajaca gracza
    char gracz;

    //zmienna okresla numer pola wybrany przez gracza
    unsigned long int wybor;

    //tablica "zajete" mowi czy dane pole zostalo juz wtkorzystane przez innego gracza
    //0=wolne pole
    //1=zajete pole
    bool zajete[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };

    //glowna petla gry
    for (int i=1; i<=9; i++)
    {
        //itrukcja decyduje o kolejnosci gracza na podstawie numeru tury
        if (i%2 == 1) gracz = 'O';
        else gracz = 'X';

        tura(gracz, plansza, wybor, zajete);
        rysowanie(plansza);

        if (wynik(plansza) == 'O') { printf("\nWygralo KOLKO");  break; }
        if (wynik(plansza) == 'X') { printf("\nWygral KRZYZYK");  break; }
        else ;

    }

    printf("\nKoniec gry\n");

    return 0;

}

void rysowanie(char plansza[3][3])
{
    printf("\n");
    printf("%c|%c|%c\n", plansza[0][0], plansza[0][1], plansza[0][2]);
    printf("-----\n");
    printf("%c|%c|%c\n", plansza[1][0], plansza[1][1], plansza[1][2]);
    printf("-----\n");
    printf("%c|%c|%c\n", plansza[2][0], plansza[2][1], plansza[2][2]);

    return 0;
}

void tura(char gracz, char plansza[3][3], int wybor, bool zajete[9])
{
    if (gracz=='O') printf("\nTura gracza KOLKO\n");
    else printf("\nTura gracza KRZYZYK\n");

    //petla sprawdza czy pole wybrane przez gracza bylo juz wczesniej zajete
    do
    {
       printf("Podaj numer pola: ");
       scanf("%d", &wybor);

       if (zajete[wybor-1] == 1 || (wybor<1 || wybor>0)) printf("\nWybierz inne pole\n");
       else ;

    } while (zajete[wybor-1] == 1 || (wybor<1 || wybor>9));

    switch (wybor)
    {
        case 1: plansza[2][0]=gracz;
        break;

        case 2: plansza[2][1]=gracz;
        break;

        case 3: plansza[2][2]=gracz;
        break;

        case 4: plansza[1][0]=gracz;
        break;

        case 5: plansza[1][1]=gracz;
        break;

        case 6: plansza[1][2]=gracz;
        break;

        case 7: plansza[0][0]=gracz;
        break;

        case 8: plansza[0][1]=gracz;
        break;

        case 9: plansza[0][2]=gracz;
        break;

        default: printf("\nWybierz numer pola z przedzialu 1-9\n");
    }

    zajete[wybor-1]=1;

    return 0;


}

char wynik(char plansza[3][3])
{
    //sprawdzanie kolumn
    if (plansza[0][0] == plansza[1][0] && plansza[0][0] == plansza[2][0]) wygrana = plansza[0][0];
    if (plansza[0][1] == plansza[1][1] && plansza[0][1] == plansza[2][1]) wygrana = plansza[0][1];
    if (plansza[0][2] == plansza[1][2] && plansza[0][2] == plansza[2][2]) wygrana = plansza[0][2];

    //sprawdzanie wierszy
    if (plansza[0][0] == plansza[0][1] && plansza[0][0] == plansza[0][2]) wygrana = plansza[0][0];
    if (plansza[1][0] == plansza[1][1] && plansza[1][0] == plansza[1][2]) wygrana = plansza[1][0];
    if (plansza[2][0] == plansza[2][1] && plansza[2][0] == plansza[2][2]) wygrana = plansza[2][0];

    //srpawdzanie po ukosie 1-5-9
    if (plansza[2][0] == plansza[1][1] && plansza[2][0] == plansza[0][2]) wygrana = plansza[2][0];

    //sprawdzanie po ukosie 7-5-3
    if (plansza[0][0] == plansza[1][1] && plansza[0][0] == plansza[2][2]) wygrana = plansza[0][0];

    return wygrana;
}

Dla osób, które preferują kolorową składnię z numerowaniem linii kod wrzuciłem tutaj: http://wklej.org/hash/90640f1e8a8/

Pętla do ...while z warunkiem (zajete[wybor-1] == 1 || (wybor<1 || wybor>9)) zabezpiecza program przed podaniem złego numeru pola (zbyt dużego albo małego). Na małych liczbach np. 100 wszystko działa bez problemu ale kiedy podać mu jakąś dużą liczbę np 100 000 wtedy program wywala mi się z komunikatem o naruszeniu ochrony pamięci. Zmienna "wybor" jest typu unsigned long int, więc powinna bez problemu obsłużyć o wiele większe liczby (przynajmniej wg tego co znalazłem w internecie tak jest).

Nie mam pojęcia co wywołuje ten błąd więc liczę na pomoc bardziej doświadczonych programistów C.

Aha, jeśli ktoś chcę kompilować ten program to niech załączy przy kompilacji w GCC zgodność ze standardem C99.

Z góry dziękuję za pomoc.

: 28 grudnia 2010, 13:21
autor: krazy
Na początek funkcje void nie zwracają wyniku, więc po prostu czytaj ostrzeżenia przy kompilacji.

A kolejna sprawa to czym jest tablica? Tablica jest to obszar pamięci o ustalonej długości (przynajmniej w C). Zrobiłeś tablicę zajete o rozmiarze 9. I teraz spójrz na swój warunek:

Kod: Zaznacz cały

(zajete[wybor-1] == 1 || (wybor<1 || wybor>9))
Skarżysz się, że jeśli zmienna wybor ma za dużą wartość to program się sypie. On powinien już sypać się przy mniejszych wartościach, takich jak 100.

Wracając do tematu.
Najważniejsze jest to, że w alternatywie oraz koniunkcji warunki są sprawdzane po kolei aż do upewnienia się jaki będzie wynik logiczny wyrażenia.
Jeśli do zmiennej wybor przypisze wartość np. 40 to w tym warunku odwołuję się do zajete[39] i sprawdzam, czy wartość wynosi 1. No, ale przecież moja tablica ma indeksy od 0 do 8, a program odwołuje się do indeksu 39. Dopiero w następnych warunkach sprawdzam zakres zmiennej wybor. Jak na moje oko powinno być na odwrót, czyli najpierw sprawdzanie zakresu, a następnie sprawdzanie wartości w tablicy o zadanym indeksie.

A dlaczego program się wysypuje przy większych wartościach, a przy mniejszych nie? Bo tablica tak naprawdę jest stałym wskaźnikiem, a wskaźniki wskazują na pewien obszar pamięci. Najprościej mówiąc system operacyjny pozwala działać programom w obrębie własnego przydziału pamięci (dlatego też miałeś szczęście, że przy podaniu liczby 100 program jeszcze się nie wysypał), ale nie pozwala grzebać w przydziale pamięci innych programów (przynajmniej nie powinien, i dlatego tutaj podając duże liczby miałeś pecha).

: 28 grudnia 2010, 16:41
autor: bigben
Wielkie dzięki za pomoc. Obecnie pętla do...while tak wygląda:

Kod: Zaznacz cały

    //petla sprawdza czy pole wybrane przez gracza bylo juz wczesniej zajete
    do
    {
       printf("Podaj numer pola: ");
       scanf("%d", &wybor);

       if ((wybor<1 || wybor>9) || zajete[wybor-1] == 1) printf("\nWybierz inne pole\n");
       else ;

    } while ((wybor<1 || wybor>9) || zajete[wybor-1] == 1);
W takiej postaci wszystko jest już OK i program nie wysypuje się. Poprawiłem również te ostrzeżenia z funkcjami void. Trzeba było po prostu usunąć "return 0;". Dodałem je bo gdzieś wyczytałem że ich dodanie na końcu funkcji void sprawia że program jest bardziej przenośny (podobno niektóre kompilatory bez return 0 nie skompilują funkcji void).

Obecnie kompilator zgłasza mi tylko jeden błąd związany ze zmienną "wybor". Chodzi mu o to że zainicjowałem zmienną i przekazałem ją do funkcji "tura" bez wcześniejszego przypisania wartości do tej zmiennej. Pomijam ten błąd bo zmienna "wybor" i tak dostaje jakąś wartość podczas pracy zmiennej "tura".

Jeśli mógłbyś to zerknij jeszcze na mój kod i napisz co można było zrobić lepiej. Na razie uczę się C i nie mam nikogo pod ręką kto mógłby sprawdzić jakość mojego kodu.

: 09 stycznia 2011, 20:23
autor: krazy
Na początek zobacz, że masz dwie tablice określające planszę, jedna, czy pole jest zajęte, druga, jeśli pole jest zajęte to przez kogo.

Najlepiej będzie to rozpykać tak, aby stworzyć tylko jedną tablicę oraz zrobić sobie własny typ wyliczeniowy (enum), w którym określasz, czy pole jest wolne, czy zajęte przez X, czy zajęte przez O.