Po wyjściu z funkcji nie mogę wypisać nic z tablicy napisów – dlaczego?

0

Cześć,

mam następujący problem, nie wiem dlaczego po wyjściu z funkcji getname, nie mogę wypisać nic z tablicy names. Wydaje mi się, że modyfikacja tablicy names w funkcji powinna być taka sama w mainie. Printf użyty w funkcji dobrze wszystko wypisuje, a ten na zewnątrz nic, albo dostaję Segmentation fault. Poniżej kod całego programu. Nie wiem na ile to ma znaczenie, ale kompiluję to na wirtualnej maszynie na Ubuntu.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <errno.h>

#define MAX_NAMES 200
#define MAX_NAME_LENGTH 40
#define RANDOM_FILENAME "names_random.txt"


#define ERR(source) ( perror(source), fprintf(stderr, "%s:%d", __FILE__, __LINE__), \
                      exit(EXIT_FAILURE) )


// call: ./program -n file.txt (every name in a different line)
// result: printing all the names

void getnames(FILE* f, char ** names, int * numberOfNames);

int main(int argc, char** argv)
{
    char c;
    int numberOfNames = 0;
    char* names[MAX_NAMES];
    FILE* f;
    int n_count = 0, e_count = 0, b_count = 0;
    while ( (c = getopt(argc, argv, "n:e:b:")) != -1)
    {
        switch(c)
        {
            case 'n':
                n_count++;
                if (n_count > 1)
                    ERR("Too many -n options");
                if (e_count > 0)
                    ERR("-e option before -n option");
                if (b_count > 0)
                    ERR("Cannot use -b option with the -n option");
                if ( (f = fopen(optarg, "r")) !=  NULL)
                {
                    getnames(f, names, &numberOfNames);
                    printf("%s\n", names[0]);
                    if (fclose(f) != 0) ERR("closing names file failed");
                }
                else
                    ERR("cannot open names file parsed as a -n argument");
                break;
            case 'e':
                break;
            case 'b':
                break;
            case '?':
                break;
        }
    }
    return EXIT_SUCCESS;
}

void getnames(FILE* f, char ** names, int* numberOfNames)
{
    char singleName[MAX_NAME_LENGTH];
    while (fgets(singleName, MAX_NAME_LENGTH, f) != NULL)
    {
        names[*numberOfNames] = singleName;
        printf("%s", names[(*numberOfNames)++]);
    }
}
0

Program nie działa, bo próbujesz odwołać się do niezarezerwowanej pamięci.

 char* names[MAX_NAMES];

To jest tablica wskaźników typu char. Zawiera wskaźniki na niezainicjalizowaną pamięć. Aby przechowywać w niej łańcuchy znaków, musisz dynamicznie przydzielić pamięć, na każdy nowo umieszczony napis.

Jeśli utworzysz wskaźnik na łańcuch znaków:

char *pstr = NULL;

/*
 * Wrong way:
 * pstr = "Hello World";
 */

/*
 * Good way:
 */
 pstr = malloc((STR_SIZE + 1) * sizeof(char));
 pstr = "Hello World";

Ponadto:

const char* pstr = "Hello World"; 
/*
Działa, bo napis istnieje w pamięci. Ma swój adres i przydzieloną odpowiednią ilość pamięci. Natomiast nie wolno modyfikować tego co zapisane jest pod adresem wskazywanym przez pstr
*/

char pstr[] = "Hello World";
/*
* Działa, bo tworzymy tablicę o rozmiarze równym strlen("Hello World") + 1. Tablica możemy zmieniać zawartość, o ile nie przekroczymy rozmiaru tablicy.
*/
0

Zmień

 char* names[MAX_NAMES];

na

 char names[MAX_NAMES];

zmodyfikuj też getNames odpowiednio.

0

@MasterBLB:
Wtedy będzie mógł przechowywać tylko jeden łańcuch, a chyba nie o to chodzi.

Bez korzystania z dynamicznego alokowania pamięci:

char names[MAX_NAMES][MAX_NAME_LENGTH]; 

I odpowiednia zmiana funkcji getnames, zależnie od tego, czy chce wypełniać jeden wiersz, czy chce wypełniać całą tablicę.

0

Fakt, tak właśnie będzie. No cóż, niech autor się wypowie precyzyjnie jaki efekt chce uzyskać.

0

Dzięki za odpowiedź, chodziło mi o tablicę napisów tak jak to opisał @Sparrow-hawk, zaalokowałem i teraz faktycznie jest w porządku. Jeszcze jedna uwaga, dla kogoś kto może kiedyś będzie korzystał z tego wątku. W funkcji gename przypisuję napisy w taki sposób:

names[*numberOfNames] = singleName;

Jest to błędne w myśl powyższego rozumowania ( i również w praktyce, bo program się sypie). Fajnie, żeby ktoś potwierdził moje rozumowanie, a jest ono następujące. W tym przypadku, singleName jest lokalną tablicą znaków, która znika po wywołaniu funkcji. W wywołaniu getnames, przypisuję do tablicy names wskaźnik, który po wyjściu z funkcji będzie wskazywał na jakieś śmieci (bo singleName jest tablicą lokalną i po wyjściu z funkcji jest ona niszczona). Dodatkowo zauważmy, że singleName się zmienia w każdym cyklu pętli, zatem jeżeli bym w tej samej funkcji przerzucił wypisywanie do drugiej pętli, która następuje zaraz po while, to otrzymałbym ostatnią wartość singleName powtórzoną numberOfNames razy. Wynika to właśnie z tego, że każdy z elementów names przechowuje wskaźnik na tę samą tablicę. Poprawne będzie zatem wykonanie kopiowania zamiast przypisania w taki sposób:

strcpy(names[(*numberOfNames)], singleName);
0

Zgadza się, w tym wypadku należy skorzystać z kopiowania. A najlepiej zrobić to jeszcze tak:

strncpy(names[(*numberOfNames)], singleName, MAX_NAME_LENGTH - 1);

1 użytkowników online, w tym zalogowanych: 0, gości: 1