Double Pointer

Thread Starter

Kittu20

Joined Oct 12, 2022
511
Also, do you spot something that I am NOT doing here that I should (as part of proper housekeeping)?
after the memory is allocated you are not checking whether the memory is allocated or not allocated.
you are not releasing memory after it has been used

Run it and see if you can spot a behavior that you probably don't expect.
I ran code on my system
C:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define BUFFERSIZE 20

int main(void)
{
   
    char buffer[BUFFERSIZE];
 
    printf("How many players? ");
    fgets(buffer, BUFFERSIZE, stdin);
    int players = atoi(buffer);
 
    char **playerNames = NULL;
 
    playerNames = (char **) malloc(players * sizeof(*playerNames));
   
    if ( playerNames != NULL) // Memory allocated
    {
        for (int player = 0; player < players; player++)
        {
            printf("Name for player #%i: ", player);
            fgets(buffer, BUFFERSIZE, stdin);
            playerNames[player] = (char *) malloc(sizeof(char) * strlen(buffer));
            strcpy(playerNames[player], buffer);
        }

        printf("Here are your players:\n");
        for (int player = 0; player < players; player++)
        {
            printf("%i) %s\n", player, playerNames[player]);
        }
       
        free(playerNames);
    }
     
    return 0;
}
How many players? 3
Name for player #0: bob
Name for player #1: devid
Name for player #2: mack
Here are your players:
0) bob

1) devid

2) mack


When I get back later today I can explain why it is doing that and how to deal with it. That will also put be into a position to tell you how you can deal with a really, really long name.
you are declaring pointer to pointer type char. we are allocating memory for playerNames

I don't understand what does following lines do in code
Code:
  playerNames = (char **) malloc(players * sizeof(*playerNames));
Code:
playerNames[player] = (char *) malloc(sizeof(char) * strlen(buffer));
 

dcbingaman

Joined Jun 30, 2021
1,065
after the memory is allocated you are not checking whether the memory is allocated or not allocated.
you are not releasing memory after it has been used



I ran code on my system
C:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define BUFFERSIZE 20

int main(void)
{
 
    char buffer[BUFFERSIZE];

    printf("How many players? ");
    fgets(buffer, BUFFERSIZE, stdin);
    int players = atoi(buffer);

    char **playerNames = NULL;

    playerNames = (char **) malloc(players * sizeof(*playerNames));
 
    if ( playerNames != NULL) // Memory allocated
    {
        for (int player = 0; player < players; player++)
        {
            printf("Name for player #%i: ", player);
            fgets(buffer, BUFFERSIZE, stdin);
            playerNames[player] = (char *) malloc(sizeof(char) * strlen(buffer));
            strcpy(playerNames[player], buffer);
        }

        printf("Here are your players:\n");
        for (int player = 0; player < players; player++)
        {
            printf("%i) %s\n", player, playerNames[player]);
        }
     
        free(playerNames);
    }
   
    return 0;
}
How many players? 3
Name for player #0: bob
Name for player #1: devid
Name for player #2: mack
Here are your players:
0) bob

1) devid

2) mack




you are declaring pointer to pointer type char. we are allocating memory for playerNames

I don't understand what does following lines do in code
Code:
  playerNames = (char **) malloc(players * sizeof(*playerNames));
Code:
playerNames[player] = (char *) malloc(sizeof(char) * strlen(buffer));
malloc allocates memory at runtime (as compared to compile time). The argument to malloc is how many 'bytes' of memory need to be reserved, malloc then returns a pointer to that memory for the programs use. It is the operating system that provides the space on an entity called the 'heap'.

The first line of code allocates sufficient memory to store all the pointers to the players names.
sizeof(x) returns how many bytes an object uses in memory. We need to multiply this by the number of players to determine the number of total bytes required for storing the pointer array that points to the player names.

The second line of code allocates sufficient memory to store each string for the names of the players.
strlen just returns the number of bytes in a null terminated string, though I think there is a problem with that because it should allocated sufficient memory not just for the string but also the null terminator.

This 'bug' often goes unnoticed because many operating systems allocate memory in 'chunks', the size of which depends on the OS. Most of the time the 'chunk' is large enough, but other times the string may extend beyond the memory allocated, but only on rare occasions since we only need one extra byte. But this code will eventually throw a memory exception, it is just a matter of time and chance.
 
Last edited:

WBahn

Joined Mar 31, 2012
32,874
after the memory is allocated you are not checking whether the memory is allocated or not allocated.
you are not releasing memory after it has been used
Correct -- and very good that you caught both of those. The first one is really important as failing to do so can cause serious problems. Not releasing memory can cause your program to run out of memory, but the memory will get freed when the program ends, so it's not a big deal on a small program like this. But it is a good practice to always do it if, for no other reason, so that you are in the habit of doing it and therefore less likely to fail to do it when it matters.
[/CODE]

How many players? 3
Name for player #0: bob
Name for player #1: devid
Name for player #2: mack
Here are your players:
0) bob

1) devid

2) mack
[/CODE]
What I was hoping you would notice is the blank like between the outputs that, by looking at the printf() statements, seems like it should be there.

The reason that they are there is because fgets() captures the newline character when you hit ENTER. This is something that scanf() strips off when you input most, but not all, things.

That newline character is there for a reason -- it's how you know that you got the entire input that the user entered. This is because fgets() takes an argument that is the maximum number of characters to read. It then reads either everything that is entered, or at most one less than this (in order to make room for the NUL terminator). So if you tell it to only read in 20 characters, but the user typed 30, the call to fgets() will only get the first 19. But the last character, before the NUL, will NOT be a newline character, letting you know that there is still more data in the keyboard buffer. So you can call it again to get the rest -- and keep calling it until you have it all. But it is your responsibility to deal with the presences of that newline character, usually by overwriting it with a NUL.

I don't understand what does following lines do in code
Code:
  playerNames = (char **) malloc(players * sizeof(*playerNames));
Code:
playerNames[player] = (char *) malloc(sizeof(char) * strlen(buffer));
So playerNames is an array of pointers. The type of the pointers stored in this array are pointers to strings, which means that they are pointers to a character.

We can't store the actual strings in the array, because arrays need to be object that are all of the same size and we have no idea how long each string is. So we store the string in memory someplace and store the address of where it is stored in memory.

This is actually the same thing that happens when you create an array of string literals.

Now, one thing that I didn't do (was curious if you would catch it) is something that is very easy to forget to do, and that's failing to account for the NUL terminator when allocating memory for the string. The value returned by the strlen() function does NOT include the NUL terminator -- and this is the behavior that we almost always want. But it's up to us to be sure that we don't forget it. This is a very subtle bug that often causes no problems for a very long time (like, in code that has been in commercial release for more than a decade) before it rears its ugly head.

It did this to be able to then emphasize the value of abstraction and modularization. What we want to do is write a function that we call when we want to get a string from the keyboard and it returns a pointer to the entered string (or a NULL pointer if it fails). All of the issues involved in making this happen, from calling fgets() multiple times, to getting rid of the newline, to making sure that the allocated space includes the NUL terminator, are dealt with in one place -- and if we forget something or need to bolster something later, we only have to do so in one place and everything gets the updated code the next time it is compiled.
 
Top