1

So... in the past I've been told that my questions aren't good... I believe mostly because I haven't isolated out problematic code well enough. I'll do my best to ask a pointed, concise, and to the point question in this post. I'm certainly open to suggestions about how my question could be asked better. Thanks.

I'm working on a small project in C that will serve as a prototype for a larger, buggy program that I've been working on for some time. I'm trying to work out the details in a smaller program first. I have two structs:

struct list
{
    char ownerName[20];
    int ownerAge;
    char sex;
}owner;

and

struct list2
{
    char petName[20];
    char owner[20];
    char animal[4];
    char breed[50];
    char color[20];
}pets;

The program is supposed to fgets ownerName from user input and compare it to ".owner" in the pets struct. The ownerName and petName elements should then be copied into an array, and the name of the owner and his/her pets will be printed in a list. While I'm aware I don't need the owner struct to accomplish this, I'm using it to model the other program I'm writing.

I'm using

if (strcmp(pets[i].owner, name) == 0) 

to compare the struct elements and seem to have this part down.

The variable j counts the number of records that meet this criteria, and the variable l = j + 1. I call the array using:

char *petsList[l];

The size of the array is dictated by l (j + 1) because I need j elements for the petNames + 1 element for the owner name.

I've also created a pointer to the petsList array via the following:

char *(*ptr)[l] = &petsList

The owner name is added to the array via the following command:

(*ptr)[0] = (char *)malloc(sizeof(name));
strcpy ( (*ptr)[0], name);

The petNames are added to the the array petsList using a for loop. I've initialized i = 1 to prevent petsList[0] from being overwritten and am trying to write petNames to the array via the following loop:

 i = 1;

        for (k=0; k < PETS; k++)
        {
            if (strcmp(pets[k].owner, name) == 0)
            {
                (*ptr)[i] = (char *)malloc(sizeof(pets[k].petName));
                if (!*(ptr)[i])
                {
                    puts("\nMemory Allocation Error");
                    exit (1);
                }
                strcpy( (*ptr)[i], pets[k].petName);
                i++;
             }
         }

Let's say for a given input of name, I get three pets that match. The loop iterates the first two times just fine, but then on the third iteration of the loop, I get a memory allocation error. This happens on the last iteration of the loop consistently. For example, if I have 2 pets associated with the ownerName, the list will run the first iteration fine and fail on the second; if I have 4 pets associated with the ownerName, the loop will run fine the first 3 times and fail on the fourth, so it appears that the final iteration of the loop consistently fails. I've tried changing the code a number of times, but am now at a loss for how I can move forward with this program. Any help is greatly appreciated.

Thanks.

Matt
  • 43
  • 7
  • What is the size of the `*ptr` array? Is `i` a valid index? And [in C you should not cast the return of `malloc`](http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc). – Some programmer dude Nov 20 '14 at 17:30
  • What does [`valgrind`](http://valgrind.org/) have to say about what the program is doing wrong. Dynamic allocation of VLAs is doable, but tricky stuff; I'd have to look very hard at the code to know whether it is correct and don't have the time right now. Do consider the merits of an MCVE ([Minimal, Complete, Verifiable Example](http://stackoverflow.com/help/mcve)) or SSCCE ([Short, Self-Contained, Correct Example](http://sscce.org/)). – Jonathan Leffler Nov 20 '14 at 17:31
  • `sizeof(name)` doesn't get the length of the string, but the size of a `char *`. Try `malloc(strlen(name)+1)`. Also, why do you need a pointer to `petsList` rather than using it directly? And if you do, you could use `char **ptr = petsList;` and access the `i`th `char *` with just `ptr[i]`, which is probably easier (to read and to use). An array pointer like you have might make more sense for an array of arrays, but that's not what you've got. – Dmitri Nov 20 '14 at 17:42
  • This question is still WAY too long. Do we really need to know about pet name, breed and colour for the purpose of demonstrating memory allocation? – Kerrek SB Nov 20 '14 at 17:42
  • @JoachimPileborg The size of the array isn't set. If 3 pet names are associated with the owner, the array size is 4, 3 for the petNames and one for the ownerName. I use j to count the instances of matching petNames and l (equal to j + 1) to declare the size of the array. I believe that i is a valid index; it seems to iterate through the loop a couple of times. What would render i invalid as an index? Thanks for your response. – Matt Nov 20 '14 at 17:43
  • @JonathanLeffler Thanks for your response. I was unfamiliar with valgrind until I read your comment, but I'll certainly have a look at it and try to implement it. I will further have a look at the links you've included as well. Thanks for your response. – Matt Nov 20 '14 at 17:46
  • @Dmitri Thanks for your reponse. The point about sizeof(name) is well taken. Thanks. I opted to use the pointer to petsList to solve some of the warnings I was getting from my compiler. I was getting a lot of 'expected char * but argument is char** type warnings. Furthermore, since I suck at pointers I try to use them when I can. Finally--and I could be wrong about this--I believe the larger program I'm working on needs to use pointers, so I figured I should work it out in this simpler program. Do you think my allocation difficulties would disappear if I was using petsList directly? – Matt Nov 20 '14 at 17:53
  • @KerrekSB Perhaps you don't need to know those details... I'm not sure, but I erred on the side of providing more information rather than less. – Matt Nov 20 '14 at 18:01
  • It could work with `petsList` directly or through a pointer, if the other errors are fixed. Most of the problems seem to be with using `sizeof` to get string lengths (which doesn't work), and using `!*(ptr)[i]` instead of `!(*ptr)[i]` (get the ith `char *` in the array `ptr` points at, rather than the first `char *` of the `i`th array). But you don't need a pointer to array of `char *` to access elements from a single array of `char *`... you could just use a pointer to `char *`. – Dmitri Nov 20 '14 at 18:12
  • @Dmitri So changing the malloc function as you suggested and changing the pointer to !(*ptr)[i] fixed the code. Thanks for your assistance. I appreciate it. – Matt Nov 20 '14 at 18:22

2 Answers2

0

This is a little weird. Maybe:

if(!*(ptr)[i])

should be

if(!(*ptr)[i])

?

shooper
  • 606
  • 4
  • 8
  • Thanks for your response, Shooper. The consensus seems to be that the pointer you specified in my code is unnecessary and problematic. I may try to rewrite without using that pointer. As to your response, doesn't changing the location of * alter the precedence and change what ptr is pointing to? Thanks again. – Matt Nov 20 '14 at 18:12
  • Your code is a little problematic. I had to sit there and think about what you were doing. I just figured that malloc (which you shouldn't really cast) is being assigned to (*ptr)[i], and then you go back and compare to *(ptr)[i]. I didn't want to think about it too much to be honest. – shooper Nov 20 '14 at 18:19
  • Actually... that seemed to help significantly. The program now runs as I expect it to. Thanks very much for your response. – Matt Nov 20 '14 at 18:19
  • Coding for me is always a work in progress. I'll probably rewrite this program a couple of ways... without pointers for example to try and better understand and to make the code more efficient. I very much appreciate your input. Thanks. – Matt Nov 20 '14 at 18:24
0

Don`t cast the malloc return value.

Since I can`t really check with a minimal example.

char *(*ptr)[l] = &petsList

why do you make such a complex construct? I am not even sure what it is supposed to accomplish. do you want all pets in the first index and the next index to contain the owner? this could be accomplished just with the petslist

what structure do you really need at the end?

is it something like:

array:

0 = owner
1 = pet 1
2 = pet 2

or something like

0,0 = owner       1,0 = owner 2        etc.
0,1 = pet 1       1,1 = pet 3
0,2 = pet 2       1,2 = pet 4

Ok here is a working example of what you want to do. you can easily extend it to do the second data arrangement. If you have any questions feel free to ask

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

#define PETAMOUNT   40

struct list2
{
    char petName[20];
    char owner[20];
    char animal[4];
    char breed[50];
    char color[20];
};

int main() {
    struct list2 *pets;             // list of all pets
    char name[128];                 // contain name of the owner, get from stdin

    unsigned int i;                 // i and j are both counter variable
    unsigned int j;

    fgets(name, 128, stdin);        // get string from stdin
    name[strlen(name) - 1] = '\0';  // remove newline

    pets = malloc(PETAMOUNT * sizeof(struct list2));    // allocate memory for the list of all pets
    if (pets == NULL) {
        printf("malloc err\n");
        exit(1);
    }

    for (i = 0; i < PETAMOUNT; i++) {           // initialize some pets and some owners
        strcpy(pets[i].petName, "petname ");
        strcpy(pets[i].owner, "owner ");
        pets[i].petName[7] = i + '0';           // there are PETAMOUNT of petnames. petname0, petname1 etc
        pets[i].owner[5] = (i / 4) + '0';       // there are PETAMOUNT / 4 owners. owner0 has petname0 to petname3, owner1 has petname4 to 7 etc
    }


    char ***petslist;                       // a list of list of strings or 3d char array
    petslist = malloc(sizeof(char **));     // allocate pointer to contain a double array
    petslist[0] = malloc(sizeof(char *));   // allocate a pointer to contain the name of the owner
    if (petslist[0] == NULL) {
        printf("malloc err\n");
        exit(1);
    }
    petslist[0][0] = malloc(strlen(name) + 1); // allocate memory to contain the owner
    if (petslist[0][0] == NULL) {
        printf("malloc err\n");
        exit(1);
    }
    strcpy(petslist[0][0], name);             // copy owner into the first index

    for (i = 0, j = 1; i < PETAMOUNT; i++) {      // go through all pets 
        if (strcmp(pets[i].owner, name) == 0) {   // if the owner of the current pet is the same as the inputted owner  
            petslist[0] = realloc(petslist[0], (j + 1) * sizeof(char *));   // allocate pointer for the next pet
            petslist[0][j] = malloc(strlen(pets[i].petName) + 1);           // allocate memory to contain the chars of the pet
            if (petslist[0][j] == NULL) {
                printf("malloc err\n");
                exit(1);
            }
            strcpy(petslist[0][j], pets[i].petName);    // copy the petname into the array
            j++;
        }
    }

    puts("petslist:");              // print it all out
    for (i = 0; i < j; i++) {
        printf("|%s|\n", petslist[0][i]);
    }

    exit(0);

    }

currently I always write to the [0][0] but if you realloc you can make room for more after that

rowan.G
  • 717
  • 7
  • 13
  • Thanks for your response, Rowan. In terms of that pointer: I thought that was how I had to construct a pointer to an array of strings. I do not want all the pets in the both indexes to include the owner. I want owner as petsList[0], and the pets listed at petsList[i], where i != 0. The structure I need at the end of this program is the first example, but for the larger project, I need something more like the second example. Thanks again. – Matt Nov 20 '14 at 18:10
  • Wow... thanks for your efforts with this Rowan. I very much appreciate it. I have the code working the way I've written it, but I'm going to enter and compile the code you've written and compare the two. Thanks again for your assistance. This is extremely helpful. – Matt Nov 20 '14 at 21:27