1. Staraj się stosować idiom alokacji z C:
struct X * x = malloc(sizeof(*x));
Pozwala to później na poprawianie mniejszej ilości kodu jeśli struktura się zmienia.
2. W C nie rzutuje się wyniku działania malloc(..), calloc(..) czy innych tego typu funkcji. Zwracają one void * który jest automatycznie konwertowany na docelowy typ.
3. Zastanów się poważnie jak będzie wyglądała macierz po takiej alokacji jak to zrobiłeś (i być może Cię uczono). Jest to tablica wskaźników na "rozstrzelone po adresach pamięci przestrzenie". Taka macierz jest obsługiwana bardzo wolno ze względu na słabą trafialność w cache. O wiele lepiej jest alokować 1 przestrzeń pamięci i "zbudować tablicę wskaźników" (bo jak rozumiem proszono Cię o obsługę double ** body .. czyli "dwie gwiazdki"). Takie alokowanie jest obecne we wszystkich bibliotekach obsługi macierzy (nawet w Python który nie zawsze w każdym aspekcie zwraca na to uwagę). Przykład:
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
typedef struct {
size_t col_size;
size_t ver_size;
double * * body;
} Matrix;
void initMatrix(Matrix * mtx, const size_t ver_size, const size_t col_size) {
mtx->col_size = col_size;
mtx->ver_size = ver_size;
// Alokowanie z zerowaniem bufora na dane
double * data_buff = calloc(mtx->ver_size * mtx->col_size, sizeof(*data_buff));
// Alokowanie tablicy wskaźników na wiersze dla tablicy 2D
mtx->body = malloc(mtx->ver_size * sizeof(*(mtx->body)));
// Budowanie wskaźników na poszególne wiersze w ramach bufora data_buff
for(size_t i = 0; i < mtx->ver_size; ++i) {
mtx->body[i] = data_buff + mtx->col_size * sizeof(**(mtx->body)) * i;
}
}
void destroyMatrix(Matrix * mtx) {
double * buff = *(mtx->body);
free(mtx->body);
free(buff);
}
void showMatrix(const Matrix * mtx) {
printf("Matrix: rows = %zu, cols = %zu\n", mtx->ver_size, mtx->col_size);
for(size_t row = 0; row < mtx->ver_size; ++row) {
for(size_t col = 0; col < mtx->col_size; ++col) {
printf("%10.4f ", mtx->body[row][col]);
}
putchar('\n');
}
}
int main(void) {
Matrix * mtx = malloc(sizeof(*mtx));
initMatrix(mtx, 5, 10);
// Zestaw prostackich testów
mtx->body[0][0] = 42.4242;
mtx->body[4][9] = 3.1415;
mtx->body[4][0] = 2.72;
mtx->body[0][9] = 1410.2133;
showMatrix(mtx);
destroyMatrix(mtx);
free(mtx);
return 0;
}
4. W przypadkach gdy to jest nieistotne, preferuj preinkrementację przed postinkrementacją. Nie sugeruj czytelnikowi że poprzednia wartość zmiennej ma znaczenie (argument "to zrobi sam kompilator", tu nie jest istotny bo odbiorcą jest człowiek).
5. Dobrze jest w C przemyśleć fakt kreacji struktur. Jeśli zrobisz to dobrze, dostaniesz od razu dobrą obsługę błędów. Tak mój przykład wyżej jak i Twój, nie sprawdza poprawności alokowania pamięci. A problem nie leży w "niedbalstwie" (choć może trochę :-) ), a raczej w złamaniu zasady że alokowanie i zwalnianie zasobu, powinno odbywać się na tym samym poziomie abstrakcji. Tu prosta poprawka poprzedniego kodu. Będzie obsługa i błędu i lepsza alokacja:
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
typedef struct {
size_t col_size;
size_t ver_size;
double * * body;
} Matrix;
void initMatrix(Matrix * * mtx_ptr, const size_t ver_size, const size_t col_size) {
Matrix * mtx = malloc(sizeof(Matrix));
if(NULL == mtx) {
goto ERR_0;
}
*mtx_ptr = mtx;
mtx->col_size = col_size;
mtx->ver_size = ver_size;
// Alokowanie z zerowaniem bufora na dane
double * data_buff = calloc(mtx->ver_size * mtx->col_size, sizeof(*data_buff));
if(NULL == data_buff) {
goto ERR_1;
}
// Alokowanie tablicy wskaźników na wiersze dla tablicy 2D
mtx->body = malloc(mtx->ver_size * sizeof(*(mtx->body)));
if(NULL == mtx->body) {
goto ERR_2;
}
// Budowanie wskaźników na poszególne wiersze w ramach bufora data_buff
for(size_t i = 0; i < mtx->ver_size; ++i) {
mtx->body[i] = data_buff + mtx->col_size * sizeof(**(mtx->body)) * i;
}
return;
// Obsługa błędów alokacji
ERR_2:
fprintf(stderr, "Zła alokacja wskaźników na wiersze!\n");
free(data_buff);
ERR_1:
fprintf(stderr, "Zła alokacja bufora na dane!\n");
free(mtx);
ERR_0:
fprintf(stderr, "Zła macierzy!\n");
*mtx_ptr = NULL;
return;
}
void destroyMatrix(Matrix * mtx) {
double * buff = *(mtx->body);
free(mtx->body);
free(buff);
free(mtx);
}
void showMatrix(const Matrix * mtx) {
printf("Matrix: rows = %zu, cols = %zu\n", mtx->ver_size, mtx->col_size);
for(size_t row = 0; row < mtx->ver_size; ++row) {
for(size_t col = 0; col < mtx->col_size; ++col) {
printf("%10.4f ", mtx->body[row][col]);
}
putchar('\n');
}
}
int main(void) {
Matrix * mtx;
initMatrix(&mtx, 5, 10);
if(NULL == mtx) {
fprintf(stderr, "Problem z inicjalizacją macierzy!\n");
return EXIT_FAILURE;
}
// Zestaw prostackich testów
mtx->body[0][0] = 42.4242;
mtx->body[4][9] = 3.1415;
mtx->body[4][0] = 2.72;
mtx->body[0][9] = 1410.2133;
showMatrix(mtx);
destroyMatrix(mtx);
return EXIT_SUCCESS;
}
6. Podobną obsługę alokacji warto zrobić w makeNewNode(...). Tu będzie prościej (bo nie będzie 2 x wskaźnika), bo funkcja zwraca wskaźnik, więc może w przypadku błędu, zwrócić NULL.