0

I am really struggling with understanding what is actually going on when using malloc and reallocwhile using structures in C. I am trying to solve the phonebook problem where I have to create a phonebook that can add, delete, and show entries. There can be an unlimited number of entries so I have to define my structure array dynamically. My delete feature also has to find the desired entry, shift back all entries after that, then use realloc to free last entry. This is where a basic understanding of realloc would really help me I think.

The only part I think I have working properly is the add feature and I still don't know if I set that up properly - I just know it is working when I run it. If someone on a very basic level can help me I would appreciate it.

Here is my mess of code :-(

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

void getEntry(int *);
typedef struct person {
    char fname[20];
    char lname[20];
    int number[10];
}person;
void getInfo(int *,int,person*);
void delInfo(int*,int,person*);
void showInfo(int*,int,person*);

int main(){

    int a=0;
    int i=0;
    int con=0;
    person* contacts=(person*)malloc(sizeof(person));
    person* pcontacts;
    pcontacts=(person*)calloc(0,sizeof(person));
    getEntry(&a);
    while (a!=4){
        switch (a){
            case 1:
                pcontacts=(person*)realloc(pcontacts,con* sizeof(person));
                getInfo(&con,i,contacts);
                break;
            case 2:
                delInfo(&con,i,contacts);
                break;
            case 3:
                showInfo(&con,i,contacts);
                break;
            default:
                printf("\n Error in response. Please try again: ");
                break;
        }
        getEntry(&a);
    }
    printf("\n Thank you for using the Phone Book Application!\n\n");
    free(contacts);
    return 0;
}

void getEntry(int *a1){
    int b;
    printf("\n\n\n Phone Book Application\n\n 1) Add Friend\n 2) Delete         Friend\n 3) Show Phone Book Entries\n 4) Exit\n\n Make a selection: ");
    scanf("%d",&b);
    *a1 = b;
 }
 void getInfo(int *con,int i,person*contacts){
    printf("\n Enter first name: ");
    scanf("%s",contacts[*con].fname);
    printf(" Enter last name: ");
    scanf("%s",contacts[*con].lname);
    printf(" Enter telephone number without spaces or hyphens: ");
    scanf("%d",contacts[*con].number);
    (*con)++;
    printf("\n Entry Saved.");
 }
 void delInfo(int *con,int i,person*contacts){
    char delfirst[20];
    char dellast[20];

    printf("\n First Name: ");
    scanf("%s",delfirst);
    printf(" Last Name: ");
    scanf("%s",dellast);

    for (i=0; i<*con;i++){
        if (delfirst==contacts[i].fname && dellast==contacts[i].lname){

        }
    }
}
void showInfo(int *con,int i,person*contacts){
    char nullstr[1]={"\0"};
    if (*con>0){
        printf("\n Current Listings:\n");
        for (i=0;i<*con;i++){
            if (strcmp(nullstr,contacts[i].fname)!=0){
                printf(" %s %s       %i\n",contacts[i].fname,contacts[i].lname,contacts[i].number);
            }
            else {};
        }
    }
    else {
        printf("\n No Entries\n");
    }
 }
Qix - MONICA WAS MISTREATED
  • 14,451
  • 16
  • 82
  • 145
NLhere
  • 15
  • 3
  • 1
    If you want a review, bring it to http://codereview.stackexchange.com/ If not, what is your question? – Danh Oct 13 '16 at 06:50
  • Possible duplicate of [Does realloc overwrite old contents?](http://stackoverflow.com/questions/3850749/does-realloc-overwrite-old-contents) – jforberg Oct 13 '16 at 06:54
  • Which part of the [`realloc` documentation](http://en.cppreference.com/w/c/memory/realloc) did you not understand ? – Jabberwocky Oct 13 '16 at 06:54
  • On a side note: it it not very wise to `typedef` to a name as `p`. Why not `typedef struct person { char fname[20]; char lname[20]; int number[10]; } person;` – Jabberwocky Oct 13 '16 at 06:57
  • `con` is zero on first-use in `case 1`, so *that* isn't going to bode well for sizing, and lead to *undefined behavior* further down the road. There is also absolutely no reason to send `i` down the line as a parameter, nor any reason to pass `con` by address to `showInfo`. You also neglected to include ``, required for proper declaration of `strcmp`. – WhozCraig Oct 13 '16 at 07:00
  • @Danh I guess I misunderstood the forums. I am just lost and seeking help with understanding realloc/malloc in general within the context of my phonebook problem. – NLhere Oct 13 '16 at 07:01
  • @Michael Walz Ill update the code with person to make it easier to follow for myself, I am not sure why I set it to 'p' in th first place – NLhere Oct 13 '16 at 07:04
  • And your last argument to `printf` in your show function doesn't match the format specifier you're promising to fulfill. You promise to send an `int`, and instead send `int*` – WhozCraig Oct 13 '16 at 07:06
  • @WhozCraig Yeah I was getting errors when trying to show the files, it wasn't working properly. I understand I need to review the basics a lot more. Thank you for your input. – NLhere Oct 13 '16 at 07:09

2 Answers2

1

Firstly, let me clarify a point that was made in another answer:

A word of caution concerning portability: according to the The GNU C Library Reference Manual, implementations before ISO C may not support this behavior.

Unless you are working with decades old embedded hardware and their proprietary compilers, you will rarely (if ever) run into a compiler that doesn't support at least the C89 (ANSI C) standard.


Now that's out of the way, let's move on to malloc() and realloc(). To understand the latter, let's look at the former.

The man page for malloc() states the following:

The malloc() . . . [and] realloc() . . . functions allocate memory. The allocated memory is aligned such that it can be used for any data type. . . The free() function frees allocations that were created via the preceding allocation functions.

That kind of helps us. Basically, your operating system "owns" all available memory physically installed in your computer. All of the RAM, swap space, etc. is all allocated by the system at the kernel level.

However, processes in user space (e.g. your program) don't want/need to manage all of the memory in the system - not to mention the security risks involved (think if I could just willy nilly edit the memory in another process with no security measures!).

Thus, the operating system will manage a process' memory and allocate different parts of the system's memory for the process to use.

Generally speaking, the operating system divides up the memory into what is called a block or a page. Simply put, if you divide your memory up into appropriate sizes (generally an exponent of 2, and at the very least a multiple of the CPU's register size) you get some very steep optimizations since the CPU can only access memory at certain granularities.

Due to all of this, the program itself must ask the kernel that memory be allocated to it so the program can use it and ensure that it is the only process that has access to it.

malloc() is the standard function call that performs this task. When you call malloc() with a length, the system will attempt to find a contiguous range of unused system memory and maps it into the process' virtual memory space, storing the address and the length of the memory that you requested.

If it can't find enough memory, it simply returns NULL (0) - as you can read in the man page :)

Subsequently, when you call free(), it looks for the address in the virtual memory table, retrieves the length that you originally malloc()'d, and tells the system that the process no longer needs that memory region.

One last point: a segmentation fault is oftentimes caused by the access of memory no longer allocated, even though it once was. This is why C++ paradigms such as RAII were invented - simply to reduce the likelihood of prematurely freeing memory, or not freeing it at all (i.e. using up a bunch of memory that isn't deallocated until the process returns).


Armed with the above knowledge, realloc() should be a piece of cake. Once again, let's take a look at its man page:

The realloc(ptr, size) function tries to change the size of the allocation pointed to by ptr to size, and returns ptr. If there is not enough room to enlarge the memory allocation pointed to by ptr, realloc() creates a new allocation, copies as much of the old data pointed to by ptr as will fit to the new allocation, frees the old allocation, and returns a pointer to the allocated memory. If ptr is NULL, realloc() is identical to a call to malloc() for size bytes. If size is zero and ptr is not NULL, a new, minimum sized object is allocated and the original object is freed.

It's kind of long, but basically tells you exactly what you need to know.

realloc() is very similar to malloc() in that you pass it a length, but you also pass a previously malloc()'d pointer to it to resize.

In short, you're basically extending a region of memory you previously allocated via malloc().

The confusion is usually brought about by the fact realloc() doesn't always expand the initial memory region, and what's more, will even free() the memory region you passed into it!

Remember my point above about contiguous memory: if you asked malloc() to allocate 10 bytes, and those 10 bytes just happened to fit right between two other regions of memory that are already allocated, and then you want to resize that 10 byte region to, say, 20 bytes -- what would have to happen?

Since the memory region must be contiguous, that means it would have to find a new spot for the memory to live that could house all 20 bytes contiguously!

In that scenario, realloc() is nice enough to find the new region with enough space, copy all of the old data from the original memory region (pointed to by the pointer you supplied realloc()), free the old region, and then return the address of the new region.

This is why you must always store the return address of realloc():

char *ptr = malloc(100);
ptr = realloc(ptr, 400); // not guaranteed the first and
                         // second addresses will be the same!
Community
  • 1
  • 1
Qix - MONICA WAS MISTREATED
  • 14,451
  • 16
  • 82
  • 145
  • @Qix-- I am not sure what you are debunking. The glibc manual points out that older implementations may not support the null pointer argument behavior for `realloc()`. I did suggest that this is not generally a problem, but I find that it is good to be aware of such possibilities. Excellent point, though, emphasizing the necessity of storing the pointer returned by a call to `realloc()`. – ad absurdum Oct 13 '16 at 09:39
  • @DavidBowling Finding a pre-ISO compiler is pretty impossible nowadays. :) That was my point, not trying to debunk the fact `realloc()` will behave differently on super old compilers. – Qix - MONICA WAS MISTREATED Oct 13 '16 at 17:35
  • 1
    @Qix-- that is a fair point; I only took issue with the idea that you were debunking something I said vs. clarifying something I said. I enjoyed your thorough discussion of `malloc()` and `relloc()`, and especially wish that I would have thought to mention that you must save the returned pointer. I have seen people thinking that the reallocated memory must be in the same location as the original memory and failing to do this. This alone is enough reason for me to give your answer an upvote. – ad absurdum Oct 13 '16 at 18:29
0

One way you can handle this is by declaring a pointer to a person struct, and accessing the referenced memory like an array:

person *contacts;

You can initialize with no entries:

int num_entries = 0;
contacts = NULL;

Then, as you add entries:

num_entries++;
contacts = realloc(contacts, sizeof(*contacts) * num_entries);

Then, you can read the new contact information into the new struct, which is contacts[num_entries - 1]. As you delete entries, after copying the person structs to their new locations in the dynamic array (if the ordering of entries is not an issue, you could just copy the last entry over the one you want to delete: contacts[delete_index] = contacts[num_entries - 1]), you simply do:

num_entries--;
contacts = realloc(contacts, sizeof(*contacts) * num_entries);

You should probably check the return value of realloc() to handle allocation errors.

One nice feature of realloc() is that, when passed a null pointer, it behaves as if malloc() had been called. So, you can start with a null pointer representing an empty contacts list, and use realloc() for all additions and deletions. A word of caution concerning portability: according to the The GNU C Library Reference Manual, implementations before ISO C may not support this behavior. I take this to mean that you are safe using this with ANSI C and later. I have never had a problem with this, but maybe someone who has will chime in.

ad absurdum
  • 19,498
  • 5
  • 37
  • 60