[C] tworzenie funkcji alokującej pamięć + wskaźniki

qwerz
Użytkownik
Użytkownik
Posty: 18
Rejestracja: 16 sty 2016, o 19:20
Płeć: Kobieta
Lokalizacja: Polska
Podziękował: 11 razy

[C] tworzenie funkcji alokującej pamięć + wskaźniki

Post autor: qwerz »

Dzień dobry. Napisałam procedurę, której zadaniem jest tworzenie dwóch macierzy o podanych przez użytkownika wymiarach i wartościach, a następnie mnożenie i tworzenie macierzy wynikowej. Muszę jednak swój kod rozbić na mniejsze funkcję i mam problem z prawidłowym utworzeniem funkcji alokującej.

Kod: Zaznacz cały

void twrzenieMacierzy(){
int **A, **B, **C;
int wierszA, kolA, wierszB, kolB;
int i, j, k;

 printf("Podaj liczbe wierszy macierzy A: 
");
 scanf("%d", &wierszA);
 printf("Podaj liczbe kolumn macierzy A: 
");
 scanf("%d", &kolA);

 printf("Podaj liczbe wierszy macierzy B: 
");
 scanf("%d", &wierszB);
 printf("Podaj liczbe kolumn macierzy B: 
");
 scanf("%d", &kolB);


//alokowanie
  A = (int**)malloc(wierszA * sizeof(int));
  B = (int**)malloc(wierszB * sizeof(int));
  C = (int**)malloc(wierszA * sizeof(int));

  for (i = 0; i < wierszA; i++)
  {
   A[i] = (int*)malloc(kolA * sizeof(int));
  }

  for (i = 0; i < wierszB; i++)
  {
   B[i] = (int*)malloc(kolB * sizeof(int));
  }

  for (i = 0; i < wierszA; i++)
  {
   C[i] = (int*)malloc(kolB * sizeof(int));
  } 
}
Jak powinna wyglądać ta funkcja?
kalwi
Użytkownik
Użytkownik
Posty: 1931
Rejestracja: 29 maja 2009, o 11:58
Płeć: Mężczyzna
Lokalizacja: Warszawa
Podziękował: 145 razy
Pomógł: 320 razy

[C] tworzenie funkcji alokującej pamięć + wskaźniki

Post autor: kalwi »

Uciekła Ci literka 'o' w nazwie funkcji.
A można to zrobić np. tak:

Kod: Zaznacz cały

void createMatrices(void)
{
    float **A, **B, **C;
    size_t rowA, rowB, colA, colB;
    
    printf("Podaj liczbe wierszy macierzy A: \n");
    scanf("%zu", &rowA);
    printf("Podaj liczbe kolumn macierzy A: \n");
    scanf("%zu", &colA);
    
    printf("Podaj liczbe wierszy macierzy B: \n");
    scanf("%zu", &rowB);
    printf("Podaj liczbe kolumn macierzy B: \n");
    scanf("%zu", &colB);
    
    //alokowanie
    A = allocMem(rowA, colA);
    B = allocMem(rowB, colB);
    C = allocMem(rowA, colB);
    
    // jeszcze trzeba zwolnić pamięć
}

float **allocMem(size_t rows, size_t columns)
{
    float **matrix = malloc(rows * sizeof(float *));
    for(size_t i = 0; i < rows; ++i)
        matrix[i] = malloc(columns * sizeof(float));
    return matrix;
}
Jeśli nie spotkałaś się z typem size_t - jest on właśnie stworzony do tego typu zmiennych, które mają być czegoś rozmiarem (np. rozmiarem tablicy) - czyli jest to typ całkowity dodatni.
W praktyce na większości maszyn size_t jest równoważny unsigned long - ale piszę się to tak, aby kod był czytelniejszy. Po prostu dobrze jest sobie wyrabiać takie nawyki.
Żeby kod się skompilował, należy załączyć bibliotekę <stddef.h>.
qwerz
Użytkownik
Użytkownik
Posty: 18
Rejestracja: 16 sty 2016, o 19:20
Płeć: Kobieta
Lokalizacja: Polska
Podziękował: 11 razy

[C] tworzenie funkcji alokującej pamięć + wskaźniki

Post autor: qwerz »

kalwi pisze: A można to zrobić np. tak:
Zadziałało, dziękuję.

Mam jeszcze problem, bo faktycznie przydałaby się funkcja zwalniająca pamięć.

W programie mam taki oto kod:

Kod: Zaznacz cały

    for (i = 0; i < rowA; i++)
        {
            free(A[i]);
            free(C[i]);
        }
    for (i = 0; i < rowB; i++)
        {
            free(B[i]);
        }
    free(A);
    free(B);
    free(C);
 }
Próbowałam utworzyć procedurę, która zwalniałaby pamięć i wyszło coś takiego:

Kod: Zaznacz cały

void freeMem(size_t rows, size_t **matrix)
{
    size_t i = 0;
    for(i = 0; i < rows; ++i)
        free(matrix[i]);
    free(matrix);
}
W funkcji głównej wywołuję to tak:

Kod: Zaznacz cały

    freeMem(rowA,&A);
    freeMem(rowB,&B);
    freeMem(rowA,&C);
Czy to jest napisane poprawnie?
kalwi
Użytkownik
Użytkownik
Posty: 1931
Rejestracja: 29 maja 2009, o 11:58
Płeć: Mężczyzna
Lokalizacja: Warszawa
Podziękował: 145 razy
Pomógł: 320 razy

[C] tworzenie funkcji alokującej pamięć + wskaźniki

Post autor: kalwi »

qwerz pisze:W programie mam taki oto kod:

Kod: Zaznacz cały

    for (i = 0; i < rowA; i++)
        {
            free(A[i]);
            free(C[i]);
        }
    for (i = 0; i < rowB; i++)
        {
            free(B[i]);
        }
    free(A);
    free(B);
    free(C);
 }
To jest ok
qwerz pisze:Próbowałam utworzyć procedurę, która zwalniałaby pamięć i wyszło coś takiego:

Kod: Zaznacz cały

void freeMem(size_t rows, size_t **matrix)
{
    size_t i = 0;
    for(i = 0; i < rows; ++i)
        free(matrix[i]);
    free(matrix);
}
W funkcji głównej wywołuję to tak:

Kod: Zaznacz cały

    freeMem(rowA,&A);
    freeMem(rowB,&B);
    freeMem(rowA,&C);
Czy to jest napisane poprawnie?
A to już nie.

Wpierw skompiluj sobie i przeanalizuj taki kod:

Kod: Zaznacz cały

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

int main()
{
    int number = 1;
    int array1[] = {99, 100}; // tablica 2-elementowa
    int array2[2][2] = {{99,100}, {101,102}}; // tablica 2x2
    int *ptr; // wskaznik wskazujacy na element typu int

    ptr = &number;
    printf("&number: %"PRIxPTR"
number: %d

", (uintptr_t)(&number), number);
    printf("ptr: %"PRIxPTR"
*ptr: %d

", (uintptr_t)(ptr), *ptr);
    
    ptr = array1;
    printf("array1: %"PRIxPTR"
*array1: %d

", (uintptr_t)(array1), *array1);
    printf("ptr: %"PRIxPTR"
*ptr: %d

", (uintptr_t)(ptr), *ptr);
    printf("array1 + 1: %"PRIxPTR"
*(array1 + 1): %d

", (uintptr_t)(array1 + 1), *(array1 + 1));
    // (*array1 + 1) jest rownowazna array1[1]. Zauwaz, ze roznica wynosi dokladnie 4 bajty
    // pomiedzy array1[0], a array1[1] - czyli sizeof(int)
    
    printf("Ilosc elementow w array1: %zu

", sizeof(array1) / sizeof(array1[0]));
    // tudziez
    printf("Ilosc elementow w array1: %zu

", sizeof(ptr) / sizeof(ptr[0]));
    // tablice tez dzialaja i tak:
    printf("Wartosc array2[0][0]: %d

", array2[0][0]);
    printf("Wartosc **array2: %d

", **(array2));
    printf("Wartosc **(array2 + 1): %d

", **(array2 + 1));
    // TO JEST UB!! ale na 99% kompilatorach zadziala tak jak oczekiwane
    printf("Wartosc *(*(array2) + 1): %d

", *(*(array2) + 1));
    printf("Tablice mozna tez tak wyswietlac: 1[array1]: %d

", 1[array1]);
    // wynika to z faktu, ze array1[1] = *(array1 + 1) = *(1 + array1) = 1[array1]
    
    
    const size_t ZMIENNA = 4;
    int **ptr1 = malloc(ZMIENNA * sizeof(int *));
    size_t i;
    for(i = 0; i < ZMIENNA; ++i)
    {
        ptr1[i] = malloc(2 * sizeof(int));
        ptr1[i][0] = i;
        ptr1[i][1] = 2 * i;
    }
    for(i = 0; i < ZMIENNA; ++i)
        printf("%d %d
", ptr1[i][0], ptr1[i][1]);
    
    printf("Ilosc elementow w ptr1: %zu

", sizeof(ptr1) / sizeof(ptr1[0][0]));
    // a tu juz nie dziala - wynika to z faktu, ze taka metoda jest poprawna jedynie dla zmiennych
    // na stosie (czyli automatycznych). Dla dynamicznych juz nie.
    
    // zwalnianie pamieci
    for(i = 0; i < ZMIENNA; ++i)
        free(ptr1[i]);
    free(ptr1);
    return 0;
}
I spróbuj znaleźć problemy w swoim kodzie.
Jak to zrobisz, to upewnij się czy dobrze:
Ukryta treść:    
I z uwag/wskazówek ogólnych:

Kod: Zaznacz cały

 void freeMem(size_t rows, size_t **matrix)
{
    size_t i = 0;
    for(i = 0; i < rows; ++i) 
        free(matrix[i]);
    free(matrix);
}
Tutaj lepiej byłoby napisać

Kod: Zaznacz cały

for(size_t i = 0; i < rows; ++i) 
skoro używasz tej zmiennej tylko tu. Jeśli tak Ci kod nie chce się skompilować - to znaczy, że musisz włączyć standard c99 w opcjach kompilatora (po prostu wpisz nazwę programu w którym kodujesz + c99 w google i będzie).

Kod: Zaznacz cały

for (i = 0; i < rowA; i++)
        {
            free(A[i]);
            free(C[i]);
        }
Staraj się pisać ++i zamiast i++. Tu masz wytłumaczenie czemu:


No i staraj się już programować w pełni po angielsku - w każdej pracy tak będzie i za wczasu warto się tego nauczyć.
qwerz
Użytkownik
Użytkownik
Posty: 18
Rejestracja: 16 sty 2016, o 19:20
Płeć: Kobieta
Lokalizacja: Polska
Podziękował: 11 razy

[C] tworzenie funkcji alokującej pamięć + wskaźniki

Post autor: qwerz »

kalwi pisze: Staraj się pisać ++i zamiast i++. Tu masz wytłumaczenie czemu:


No i staraj się już programować w pełni po angielsku - w każdej pracy tak będzie i za wczasu warto się tego nauczyć.
Dziękuję za cenne porady. Podzieliłam już prawie cały swój program na funkcje, mam jedynie problem z ostatnią, tj. wczytywaniem wartości macierzy zapisanej w pliku do programu.

Pliki tekstowe z macierzami mają taką postać:

Kod: Zaznacz cały

2	4 
1	2	3	4	
5	6	7	8	
gdzie dwie pierwsze liczby oznaczają odpowiednio liczbę wierszy i liczbę kolumn, natomiast pozostałe liczby to wartości macierzy.

Kod, który odpowiada w moim programie za wczytywanie macierzy z pliku wygląda następująco:

Kod: Zaznacz cały

    float **A;
    size_t rowA, colA, rowB, colB;

    FILE *f;
    char name[20];
    printf("Podaj nazwe pliku, z ktorego chcesz wczytac macierz: ");
    scanf("%s",name);

    f=fopen(name,"r");
    if(f==NULL)
        {
            printf("Nie mozna otworzyc pliku!
");
            exit(0);
        }
    fscanf(f, "%d %d", &rowA, &colA);
    printf("Liczba wierszy wczytanej macierzy wynosi: %d
Liczba kolumn wczytanej macierzy wynosi: %d
", rowA, colA);

    //alokowanie pamięci
    A=(float**)malloc(sizeof(float*) * rowA);
    for(size_t i = 0; i < rowA; ++i)
        A[i]=(float*)malloc(sizeof(float) * colA);

    //wczytywanie wartości
    for(size_t i = 0; i < rowA; ++i)
        for(size_t j = 0; j < colA; ++j)
            fscanf(f, "%d ", &A[i][j]);

    fclose(f);
Miałam taki zamysł, że najpierw otwieramy plik o danej nazwie, odczytujemy liczbę wierszy i kolumn z pierwszego wiersza pliku tekstowego, następnie, znając liczbę wierszy i kolumn, alokujemy odpowiednią ilość pamięci, na końcu zaś wczytujemy wartości. Nie mam natomiast pomysłu, jak przerobić powyższy kod na funkcję, która wczytywałaby i zwracała jednocześnie liczbę wierszy i kolumn macierzy. Z alokowaniem i wczytywaniem wartości już chyba nie będzie kłopotu, bo mogę wykorzystać poprzednie funkcje, np. allocMem().
kalwi
Użytkownik
Użytkownik
Posty: 1931
Rejestracja: 29 maja 2009, o 11:58
Płeć: Mężczyzna
Lokalizacja: Warszawa
Podziękował: 145 razy
Pomógł: 320 razy

[C] tworzenie funkcji alokującej pamięć + wskaźniki

Post autor: kalwi »

Po kolei:

Kod: Zaznacz cały

f=fopen(name,"r");
    if(f==NULL)
        {
            printf("Nie mozna otworzyc pliku!
");
            exit(0);
        }
Napisałbym to tak:

Kod: Zaznacz cały

if((f = fopen(name, "r") == NULL))
{
    fputs("Nie mozna otworzyc pliku!", stderr);
    exit(EXIT_FAILURE);
}
1) im mniej linii kodu, tym lepiej - oczywiście zachowując przy tym czytelność
2) do wyświetlania błędów logiki aplikacji (czyli w razie błędów w trakcie działania programu) służy strumień stderr - standard error (standardowe wyjście na błędy)
Chodzi o to, że łatwiej wtedy odróżnić zwykły tekst wyświetlany w konsoli od informacji o błędach.
Jest jeszcze coś takiego jak funkcja perror - program error, ona służy do wyświetlania błędów w systemie (typu nie można zaalokować pamięci).
Zauważ, że fputs("Napis",stdout); jest równoważne puts("napis");
3) exit() może zwracać jedną z 2 wartości: EXIT_SUCCESS lub EXIT_FAILURE. Pierwsza z nich jest równoważna exit(0), natomiast druga exit(1) - i tak jak w przypadku size_t to sprawia, że kod się staje czytelniejszy.
Na dobrą sprawę exit() może zwracać jedną z 256 wartości, no, ale te dwie co opisałem głównie się wykorzystuje (szczególnie na początku).

Swoją drogą, to powinnaś do kodu dodać właśnie takie sprawdzenie, czy pamięć została zaalokowana. Wygladałoby to tak:

Kod: Zaznacz cały

float **allocMem(size_t rows, size_t columns)
{
    float **matrix = malloc(rows * sizeof(float *));
    checkMem(matrix);
    for(size_t i = 0; i < rows; ++i)
    {
        matrix[i] = malloc(columns * sizeof(float));
        checkMem(matrix[i]);
    }
    return matrix;
}

void checkMem(void *memory)
{
    if(memory == NULL)
    {
        errno = ENOMEM;
        perror("Nie mozna zaalokowac pamieci!
");
        exit(EXIT_FAILURE);
    }
}
void *memory oznacza, że funkcja przyjmie wskaźnik dowolnego typu (bez względu na to, czy to będzie int, float, czy cokolwiek innego). errno oznacza kod błędu - ENOMEM oznacza error no memory, czyli brak pamięci. Aby to działało, należy załączyć <errno.h>

Wracając do programu - przy wczytywaniu rozmiarów macierzy znowu lepiej byłoby dać size_t

Kod: Zaznacz cały

size_t row, col;
fscanf(f, "%zu %zu", &row, &col);
%zu to jest specyfikator dla size_t.
Nie mam natomiast pomysłu, jak przerobić powyższy kod na funkcję, która wczytywałaby i zwracała jednocześnie liczbę wierszy i kolumn macierzy
Na przykład stwórz funkcję void getDim(char *file_name, size_t *dim_array) (get dimensions, dimensions array) - i po prostu w głównej funkcji stworzysz sobie tablicę dwuelementową (np. pierwszy element to szerokość, drugi to wysokość), przekażesz ją do funkcji i tam wypełnisz. Lepiej by to było zrobić przez strukturę - ale nie wiem czy takowe miałaś. Funkcja nie może zwracać więcej niż jednego elementu, więc zrobić to wprost się nie da.
qwerz
Użytkownik
Użytkownik
Posty: 18
Rejestracja: 16 sty 2016, o 19:20
Płeć: Kobieta
Lokalizacja: Polska
Podziękował: 11 razy

[C] tworzenie funkcji alokującej pamięć + wskaźniki

Post autor: qwerz »

Chyba się udało

Niemniej jednak poniższy fragment kodu
kalwi pisze: Napisałbym to tak:

Kod: Zaznacz cały

if((f = fopen(name, "r") == NULL))
{
    fputs("Nie mozna otworzyc pliku!", stderr);
    exit(EXIT_FAILURE);
}
sprawił mi pewne problemy, bo nie zauważyłam, że nawiasy w ifie są źle podane. Mimo to wielkie dzięki!
kalwi
Użytkownik
Użytkownik
Posty: 1931
Rejestracja: 29 maja 2009, o 11:58
Płeć: Mężczyzna
Lokalizacja: Warszawa
Podziękował: 145 razy
Pomógł: 320 razy

[C] tworzenie funkcji alokującej pamięć + wskaźniki

Post autor: kalwi »

Też nie zauważyłem

Bardzo proszę
ODPOWIEDZ