0

I am new to C and I have been stuck on this code for the whole moring.
It compiles without a problem, but fails when executed.
If you have any idea that would help me solve this, please leave me a comment. Any comment would be greatly appreciated.

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

typedef struct phonebook {
    char name[20];
    char phoneNum[20];
} Phonebook;

int bookSize=1;

void load(Phonebook **book);
void insert(Phonebook **book);
void delete(Phonebook **book);
void search(Phonebook *book);
void print(Phonebook *book);
void save(Phonebook *book);

int main(void) {
    Phonebook *book = (Phonebook *)calloc(sizeof(Phonebook), bookSize);
    load(&book);
    int menuInput=0;
    while(menuInput != 5) {
        puts("***** MENU *****");
        puts("1. Insert");
        puts("2. Delete");
        puts("3. Search");
        puts("4. Print All");
        puts("5. Exit");
        printf(">> ");
        scanf("%d", &menuInput);

        switch(menuInput) {
            case 1 : insert(&book); break;
            case 2 : delete(&book); break;
            case 3 : search(book); break;
            case 4 : print(book); break;
            case 5 : break;
            default : puts("enter correct command"); break;
        }
    }
    save(book);
    free(book);
    puts("\nexit\n");
    return 0;
}

void load(Phonebook **book) {
    FILE *fp = fopen("phonebook.txt", "rt");
    if(fp == NULL) {
        FILE *fp = fopen("phonebook.txt", "wt");
        fclose(fp);
        puts("Welcome! It looks like you don't have an existing phonebook.");
        puts("A new phonebook has been created.\n");
        return;
    }
    else {
        char temp[20];
        int i=0;
        while(fscanf(fp, "%s", temp) != EOF) {
            strcpy(book[i]->name, temp);
            fscanf(fp, "%s", temp);
            strcpy(book[i]->phoneNum, temp);
            i++;
            bookSize++;
            *book = (Phonebook *)realloc(*book, sizeof(Phonebook) * (bookSize));
        }
        fclose(fp);
        printf("Loaded %d contacts\n", bookSize-1);
    }
}

void insert(Phonebook **book) {
    puts("\nCreate a new contact");
    getchar();
    char temp[20];
    printf("Name : ");
    fgets(temp, 20, stdin);
    //temp[strlen(temp)-1]=0;
    strcpy(book[bookSize-1]->name, temp);
    //fgets(book[bookSize-2]->name, 20, stdin);
    //book[bookSize-2]->name[strlen(book[bookSize-2]->name)-1]=0;
    printf("Phone : ");
    fgets(temp, 20, stdin);
    //temp[strlen(temp)-1]=0;
    strcpy(book[bookSize-1]->phoneNum, temp);
    //fgets(book[bookSize-2]->phoneNum, 20, stdin);
    //book[bookSize-2]->phoneNum[strlen(book[bookSize-2]->phoneNum)-1]=0;
    puts("Done!\n");
    bookSize++;
    *book = (Phonebook *)realloc(*book, sizeof(Phonebook) * bookSize);
}

void delete(Phonebook **book) {}

void search(Phonebook *book) {}

void print(Phonebook *book) {
    if(bookSize == 1) {
        puts("\nempty\n");
        return;
    }
    puts("");
    for(int i=0; i<bookSize-1; i++) {
        printf("Name : %-10s  Phone : %s\n", book[i].name, book[i].phoneNum);
    }
    puts("");
}

void save(Phonebook *book) {
    FILE *fp = fopen("phonebook.txt", "wt");
    for(int i=0; i<bookSize-1; i++) {
        fprintf(fp, "%s\n%s\n", book[i].name, book[i].phoneNum);
    }
    fclose(fp);
    printf("\nSaved %d contacts", bookSize-1);
}
Segmentation fault (core dumped)

** sorry for removing parts of the code I thought was 'irrelevant'!
I have added the whole code to the post. Thanks!

seungyun
  • 88
  • 4
  • 2
    `#include`s, `Phonebook` and `bookSize` are all missing. Please post real, compilable code. – dxiv Jun 08 '20 at 03:21
  • 1
    Where's the ```struct template of Phonebook```? what is ```bookSize```? Don't you think this are the relevant parts of the code? – Shubham Jun 08 '20 at 03:25
  • I have added the whole code to the question. Thanks reminding me to post compilable code! – seungyun Jun 08 '20 at 03:31
  • 1
    If you're using Linux, you can download and use the Valgrind program. It's very handy for debugging things like segfaults. – Daniel Walker Jun 08 '20 at 03:32
  • @DanielWalker thanks for the suggestion! I am indeed using Linux. I will try the Valgrind program! – seungyun Jun 08 '20 at 03:41
  • 1
    To help out Valgrind, compile your code with the `-g` flag. That will cause the compiler to include debugging information. – Daniel Walker Jun 08 '20 at 03:42

2 Answers2

2

tl;dr: insert(&book) should just be insert(book), and define this to be the address you get from allocating memory in the heap for storing the address you get from calloc.

You define your argument for insert() as **book, and when you get a *book from your calloc() call, you reasonably "add on another *" with the address operator &. The catch is that the address of *book that you got from your calloc call is a location on the call stack of your main() function. So, when the argument of strcpy() goes to dereference this address with the array index notation, it attempts to get the value located at the pointer that's on your call stack + bookSize - 1. This is already in undefined behavior territory, since the stack isn't supposed to store memory dynamically, but you're getting the segfault because the stack is at the top of the memory layout (high address area), so adding a large enough value to the dereferenced value of book puts you in an illegal memory access zone.

nschmeller
  • 79
  • 1
  • 2
  • 9
  • 3
    You have diagnosed the issue correctly, but your solution is not viable. The OP needs to receive a double pointer because they reallocate memory, and return the new pointer via the double pointer. – John Bollinger Jun 08 '20 at 03:37
  • Thanks for the detailed explanation! As of now, due to my lack of understanding of C, I cannot fully understand what you said. But I will make sure to study your comment thoroughly! – seungyun Jun 08 '20 at 03:38
  • @JohnBollinger It seems like Nick's solution fixed the segfault and the code is operating as intended. Am I missing something? – seungyun Jun 08 '20 at 03:43
  • @JohnBollinger you're right, I didn't see the realloc call after I found where the segfault was happening, thanks for pointing it out – nschmeller Jun 08 '20 at 03:45
  • 1
    Yes, @seungyun. You are missing that Nick's solution only works so long as `realloc()` expands the allocated memory in place, which it is by no means certain to do. – John Bollinger Jun 08 '20 at 03:45
  • @seungyun The code makes it more confusing than it needs be by using the same name `book` for the array in `main` and the argument to `insert`. Because of that, `book[bookSize - 1]` means completely different things in `insert` vs. `main`. – dxiv Jun 08 '20 at 03:48
  • Edited per @JohnBollinger comment, but this solution is one of several you could do – nschmeller Jun 08 '20 at 03:48
  • @JohnBollinger oh, I just noticed that. Thanks for pointing that out! – seungyun Jun 08 '20 at 03:50
  • @NickSchmeller Thanks for the edit! I will try to fix the code using your solution. – seungyun Jun 08 '20 at 03:52
  • @NickSchmeller I don't see how the edit fixes the issue raised. As long as `insert` takes just the pointer *value* instead of its *address*, the `realloc` return value does not propagate back to `main`. What you want, instead, is leave `insert` as is but replace (for example) `book[bookSize-1]->phoneNum` with `(*book)[bookSize-1].phoneNum`. – dxiv Jun 08 '20 at 03:54
  • @dxiv correct me if I'm wrong, but I think you're talking about how I suggest `insert(book)` instead of `insert(&book)`? If that's the case, then my solution works because `book` is a `**Phonebook` when you do the second part of my solution where you put the pointer you get on the heap and pass the address to that to `insert` – nschmeller Jun 08 '20 at 03:58
  • @dxiv good point, just want to clarify that OP definitely needs to change `insert(&book)` in `main()` to `insert(book)` because otherwise it passes in an address on the stack – nschmeller Jun 08 '20 at 04:00
  • 2
    @NickSchmeller Sorry, not sure what you mean by "*put the pointer you get on the heap and pass the address to that to insert*". And there is nothing wrong with passing the *address* of `book` in `main` (which, yes, is on the stack) to `insert`. In fact, it is required if `insert` has to modify the `book` pointer in `main`, which it must do because of `realloc`. The real problem is that `insert` uses the wrong syntax to access the last element in the array. – dxiv Jun 08 '20 at 04:03
2

As your other answer indicates, you are tripping over the details of the double indirection.

You are maintaining your phone book as an array of structures. In main, variable book is a pointer to the first structure in that array. The second will immediately follow it in memory, and the third will immediately follow that, etc.. That's all perfectly fine.

Both insert() and load() accept as a parameter a pointer to the pointer to the first book. This also is right and proper, because these methods reallocate the memory for the array. Reallocation is not necessarily done in place -- the new space may be in a different location than the old. The original pointer passed into realloc must be considered invalid after the call, and the return value used in its place (supposing the call succeeds). You handle this correctly, too, updating main's pointer through the pointer argument:

           *book = (Phonebook *)realloc(*book, sizeof(Phonebook) * (bookSize));

But your attempts to write phone book entries into the allocated space is incorrect. For example, in load(), this:

           strcpy(book[i]->name, temp);

tries to access the ith Phonebook * in the array of pointers to which book points, and to write to the name member of the Phonebook to which it points. But there is only ever one Phonebook *, not an array of them. You are allocating and reallocating space for the Phonebooks to which it points.

Here's a crude diagram:


Actual layout:

[Phonebook **]  ----> [Phonebook *]  ----> [ Phonebook, Phonebook, Phonebook ... ]

Being accessed as if it were:

[Phonebook **]  ----> [Phonebook *, Phonebook *, Phonebook *, ...]
                           |            |            |
                           V            |            |
                      [Phonebook]       V            |
                                   [Phonebook]       V
                                                 [Phonebook]

Solution:

Just as you assign the allocated pointer to *book, not to book, it is *book to which you should be applying the indexing operator:

            strcpy((*book)[i].name, temp);

And since it's an array of Phonebooks, not an array of pointers to them, you use the direct member access operator (.), as shown, not the indirect access operator.

Beware, however, that you use the same name, book, in different functions to designate pointers with different degrees of indirection. Thus, whereas the above would be correct in load() and insert(), it would be wrong in main() and some of the other functions.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Thank you so much!! Your solution fixed the issue AND was very easy to understand! I now understand why my code went wrong and how your solution fixed it. I appreciate your time and effort writing this solution for a beginner like me. – seungyun Jun 08 '20 at 05:49