C, błedne obliczenia, struktury i funkcje

Zwracanie błędnych obliczeń w lini 14. 

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

struct S{
    char KLASA[20];
    float OCENY[20][5];
}szkola;
void SEREDNIA(struct S szkola,float serednia_ucznia[],float serednia_przedmiotu[]){
    int i,j;
    for(i=0;i<3;++i){
        for(j=0;j<2;++j){
            serednia_ucznia[i]+=szkola.OCENY[i][j];
        }
        printf("%f\n",serednia_ucznia[i]);
    }

}
int main()
{
    int i,j;
    float serednia_ucznia[3],serednia_przedmiotu[2];
    printf("podaj nazwe klasy:\n");
    scanf("%s",&szkola.KLASA);
    for(i=0;i<3;++i){
        printf("Uczen: %d\n",i+1);
        for(j=0;j<2;++j){
            printf("Przedmiot: %d\n",j+1);
            scanf("%f",&szkola.OCENY[i][j]);
        }
        printf("---------------------------\n");
    }
    SEREDNIA(szkola,serednia_ucznia,serednia_przedmiotu);
    return 0;
}
  1. Opisuj problemy dokładniej. Przydatne są: przykładowe dane wejściowe, przykładowy wynik aktualny, przykładowy wynik oczekiwany.

  2. Komunikaty programu nie są jasne. Po uruchomieniu jest jeden jasny komunikat - by wpisać nazwę klasy. Następnie już dla mnie nie wiadomo o co chodzi. Musiałem spojrzeć w kod, by wiedzieć co to ode mnie chce (lub się domyślać, wiedząc, co jest zadaniem tego programu)

  3. Aktualnie, wskazana funkcja liczy tylko sumę ocen i taką wartość też wyświetla. Do policzenia średniej brakuje jeszcze jednej operacji.

Tak program nie jest skończony. Nie dokonałem tego gdyż już na początku tzn w funkcji średnia źle sumuje; Przykład: 

podaj nazwe klasy:
ada
Uczen: 1
Przedmiot: 1
2
Przedmiot: 2
2
---------------------------
Uczen: 2
Przedmiot: 1
2
Przedmiot: 2
2
---------------------------
Uczen: 3
Przedmiot: 1
2
Przedmiot: 2
2
---------------------------
3150778372278305200000000000000000.000000
3152220572424072700000000000000000.000000
4.000000

Process returned 0 (0x0) execution time : 6.610 s
Press any key to continue.

Tylko dla ucznia nr 3 prawidłowo z sumował.

 

Bez znaku ampersand tutaj. Nazwa tablicy jest (niejawnie) rzutowana na adres jej pierwszego elementu. szkola.KLASA więc już jest typu char *.

 

  1. Zmienne lokalne nie są inicjalizowane.Twoje tablice serednia_ucznia, serednia_przedmiotu nie zawierają na start żadnych użytecznych informacji. Następnie do tych śmieci co tam są próbujesz coś dodawać. Tablice te należy wstępnie wypełnić zerami.

    float serednia_ucznia[3] = { 0 }, serednia_przedmiotu[2] = { 0 };

Domyśliłem się, że może przyczyną jest brak wypełnienia elementów na starcie zerami. Lecz przecież puste miejsca w tablicy są automatycznie wypełniane zerami więc po co wypełniać je jeszcze raz ?

void SEREDNIA(struct S szkola,float serednia_ucznia[],float serednia_przedmiotu[]){
int i,j;
float suma_ucznia[4];
float suma_przedmiotow[2];
for(i=0;i3;++i){
for(j=0;j2;++j){
suma_ucznia[i]+=szkola.OCENY[i][j];
}
printf("%f\n",suma_ucznia[i]);
}
}

Przy częściowej inicjalizacji pozostałe elementy są wypełniane zerami, ale jeśli w ogóle nie inicjalizujesz to nic nie zostanie wyzerowane. http://gynvael.coldwind.pl/?id=280

Czasem działa, czasem nie. Tu nie działa: http://ideone.com/4jhM1d

Dzięki wielkie trochę mi rozjaśniliście sytuacje. Myślałem, że gorzej będzie z rozwiązywaniem męczących mnie pytań dlaczego tak, a nie  inaczej.Nurtuje mnie jeszcze jeden programik którego rozwiązania doszedł po wielu próbach i godzin głowienia się. W sumie nie wiem dlaczego sposób działa:

#include <stdio.h>
#include <stdlib.h>
int F(int tab[],int n){
    int i;
    for(i=0;i<n;++i){
        tab[i]=tab[i]*tab[i]*tab[i];
    }
}
void G(int a,int b, int k,int *n,int tab[]){
    int i;
    *n=((b-a)/k)+1;
    for(i=0;i<*n;++i){
        tab[i]=a;
        a=a+k;
    }
    F(tab,*n);
}
int main()
{
    int a,b,k,i,n=0;
    printf("Podaj wartosc poczatkowa\n");
    scanf("%d",&a);
    printf("Podaj wartosc koncowa\n");
    scanf("%d",&b);
    printf("Podaj wartosc kroku\n");
    scanf("%d",&k);
    int tab[n];
    G(a,b,k,&n,tab);
    for(i=0;i<n;++i){
        printf("%d\n",tab[i]);
    }
    return 0;
}

W tym programiku nurtuje mnie linijka 27.

Nie.

Zacznijmy od konstrukcji:

int tab[n];

to jest to poprawne, od standardu C99, i nazywa się VLA. Nie ma takiego czegoś dla ANSI/ISO C (C89/C90). Także żadna wersja C++ nie definiuje VLA. Lepiej jednak tego unikać - nie każdy kompilator to obsługuje, no i zupełnie nie działa tak, jak Ci się wydaje, i próbujesz to zastosować w przedstawionym kodzie.

Jeżeli chodzi o tablicę na zero elementów. To już nie jest poprawne.

Link: http://stackoverflow.com/questions/9722632/what-happens-if-i-define-a-0-size-array-in-c-c

Jednak skoro na gcc działa, spójrzmy na sizeof():

int tab[0];
    printf("%d", sizeof(tab));

wyświetla 0! W ten sposób nie masz pamięci w ogóle. Nie możesz pod tym adresem nic pisać.

Dalej. Zmiana wartości zmiennej n nie wpływa w żaden sposób na zadeklarowaną wcześniej tablicę. Nadal masz pamięć tylko na 0 elementów. Tablica została już zadeklarowana, przydzielono jej pamięć ze stosu - “stało się”. Teraz jej rozmiaru już zmienić się nie da. Zmiana wartości zmiennej, która posłużyła do utworzenia tej tablicy nic nie da.

 

int a = 3;
    int b = 4;
    int c = a+b;
    a = 99;

To jest tak, jakbyś oczekiwał tutaj, że po zmianie wartości zmiennej “a” w czwartej linii ulegnie zmianie także suma “c”, obliczona linię wcześniej. Bez sensu, prawda?

Podsumowując - “dlaczego działa?” - otóż NIE DZIAŁA! Działa całkiem przypadkiem. Piszesz po pamięci, która nie jest Twoja - wykraczasz poza przydzieloną pamięć, czego robić nie wolno. Przy odpowiednio dużym zakresie na pewno w końcu program się wysypie - natrafisz na pamięć chronioną przez system i skończy się to crashem.

Jak będziesz miał pecha to stanie się tak bardzo szybko.

Jeżeli więc zachodzi potrzeba posiadania tablicy, którą można rozszerzyć szukać należy w innym kierunku. To już inny temat. Hasła kluczowe tutaj to: “dynamiczne alokowanie pamięci”, “zarządzanie pamięcią”, “dynamiczny przydział pamięci”. Interesujące funkcje: malloc, calloc, realloc, free.

Jeżeli potrzebujesz pamięć, której wielkość nie jest znana w momencie kompilacji także raczej skorzystaj z dynamicznej alokacji, zamiast używać VLA. Nawet jak nie potrzebujesz przydzielonego rozmiaru pamięci później zmieniać.

Więcej o VLA i różnicy pomiędzy tym, a dynamiczną alokacją: http://gynvael.coldwind.pl/?id=300