Thread: Reassigning pointer within a function

  1. #1
    Registered User
    Join Date
    Nov 2015
    Posts
    10

    Reassigning pointer within a function

    Hello,
    I'm trying to create "SmartArray" in C, which is a struct defined like this:

    Code:
    typedef struct SmartArray{
        //will store an array of strings
        char **array;
    
    
        // Size of array (i.e., number of elements that have been added to the array)
        int size;
    
    
        // Length of the array (i.e., the array's current maximum capacity)
        int capacity;
    
    
    } SmartArray;
    Now, first I allocate room for only 1 string(only pointers, not memory for strings)...

    Code:
    SmartArray *createSmartArray(int length){
        SmartArray* SA = NULL;
    
    //there are checks for malloc failure but I omitted them now to make the code clearer
        SA = (SmartArray*)malloc(sizeof(SmartArray));
    
    
        SA->array = (char**)malloc(sizeof(char*)*1);
    
    
        SA->size = 0;
        SA->capacity = length;
    
    
        return SA;
    }
    The plan is that all of the other pointers and memory be allocated when something needs to be added to this struct's array.

    This function should insert strings into the array:
    Code:
    char *put(SmartArray *smarty, char *str){
        if(smarty == NULL || str == NULL)
            return NULL;
    
    
        if((strlen(str) + getCurrentLength(smarty)) >= smarty->capacity){
            smarty = expandSmartArray(smarty, smarty->capacity*2 + 1);
        }
    
    
        smarty->array[smarty->size] = (char*)malloc(sizeof(char)*(strlen(str) + 1));
    
    
        if(smarty->array[smarty->size] == NULL)
        {
            printf("Couldn't allocate space for new string\n");
            return NULL;
        }
    
    
    
    
        strcpy(smarty->array[smarty->size], str);
    
    
        smarty->size += 1;
    
    
        return smarty->array[smarty->size - 1];
    }
    This seems to work fine, as long as the initial capacity doesn't have to be changed, but when it does, segfault occurs. Here is my expandSmartArray function:

    Code:
    SmartArray *expandSmartArray(SmartArray *smarty, int length){
        int i;
    
    
        if(length <= smarty->capacity)
            return smarty;
    
    
        SmartArray* newSmarty = (SmartArray*)malloc(sizeof(SmartArray));
    
    
        //if any of the MALLOC's fail, return NULL, again omitted from code
    
        newSmarty->array = (char**)malloc(sizeof(char*)*smarty->size);
    
    
        for(i = 0; i < smarty->size; i++)
        {
            newSmarty->array[i] = (char*)malloc(sizeof(char)*(strlen(smarty->array[i]) + 1));
    
    
            strcpy(newSmarty->array[i], smarty->array[i]);
        }
    
    
        newSmarty->size = smarty->size;
        newSmarty->capacity = length;
    
    
        smarty = destroySmartArray(smarty);
    
    
        printf("Expanded SmartArray to size %d\n", newSmarty->capacity);
    
    
        return newSmarty;
    }
    Does anyone have any idea how can I make this work?

    My main is really simple at this point:
    Code:
    SmartArray *smarty1 = createSmartArray(20);
    while (fscanf(file, "%s", buffer) != EOF)
            put(smarty1, buffer);

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,661
    Why don't you realloc smarty->array, rather than recreating a whole new container?
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Rorsh
    first I allocate room for only 1 string(only pointers, not memory for strings)...
    This sounds wrong: you have a parameter named length that is assigned to the member named capacity. It follows that you should allocate space for length (or capacity) number of pointers. Actually, it would probably be better to rename length to capacity since length sometimes means size, or could be misinterpreted as meaning the maximum length of a string in the array.

    Also, there is no need to cast the return value of malloc. I suggest that instead of:
    Code:
    SmartArray* SA = NULL;
    
    //there are checks for malloc failure but I omitted them now to make the code clearer
    SA = (SmartArray*)malloc(sizeof(SmartArray));
    You go with something like:
    Code:
    SmartArray* array = malloc(sizeof(*array));
    // then do the check for malloc failure
    Quote Originally Posted by Rorsh
    This function should insert strings into the array:
    It would be good to specify: insert meaning append, prepend, or something like insert at a given location or in sorted order? Looking at the code, this insertion function appends a copy of the given string. I suggest that you make use of name prefixes for your functions, so instead of createSmartArray, you would have say, SmartArray_create, then instead of put you would have say, SmartArray_append.

    Next, this looks strange:
    Code:
    if((strlen(str) + getCurrentLength(smarty)) >= smarty->capacity){
        smarty = expandSmartArray(smarty, smarty->capacity*2 + 1);
    }
    Why does the length of the string provided matter? You should expand when the capacity is about to exceed the size. I would have written:
    Code:
    if (smarty->size == smarty->capacity)
    {
        smarty = SmartArray_expand(smarty, smarty->capacity * 2 + 1);
    }
    Now, instead of expanding the smart array before you are ready to insert, I would have prepared a copy of the string for insertion:
    Code:
    char* new_str = malloc(strlen(str) + 1);
    if (new_str == NULL)
    {
        printf("Couldn't allocate space for new string\n");
        return NULL;
    }
    strcpy(new_str, str);
    and then expand, and finally append and return:
    Code:
    smarty->array[smarty->size++] = new_str;
    return new_str;
    I note that your expansion function has to consider the case where realloc returns a null pointer, so you may have to consider this in the calling code too.

    Quote Originally Posted by Rorsh
    Here is my expandSmartArray function:
    Since you designed the function to do nothing if the new capacity is not greater than the old capacity, it seems to me that you have over-complicated the code. You only need one realloc to increase the capacity. No additional malloc calls for the strings are needed since the size does not change. Likewise, you don't need to destroy anything.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  4. #4
    Registered User
    Join Date
    Nov 2015
    Posts
    10
    Thank you for your fast replies! I feel I didn't explain it properly.. OK, so array is 2D pointer... It will hold array of strings.. Size parameter of that struct will be a counter on how many strings are inserted, and capacity is total number of characters in all strings (basically take all strings in the array and sum their lengths strlen(array[0]) + strlen(array[1]) + ....).

    You both might be correct about realloc, I will try it that way.

    Now, if the new capacity is less or equal to the new capacity that means that that array will not be expanded in any way, so I just return, otherwise I tired to copy the contents to the new array (totaly new Struct object), but maybe it would just be better if I incremented value of capacity to new capacity("length" in function declaration)?

  5. #5
    Registered User
    Join Date
    Nov 2015
    Posts
    10
    Sorry for the double post. I did some changes, and now Expand looks like this:

    Code:
    SmartArray *expandSmartArray(SmartArray *smarty, int length){
        if(length <= smarty->capacity)
            return smarty;
    
    
        smarty->capacity = length;
    
    
        printf("Expanded SmartArray to size %d\n", smarty->capacity);
    
    
        return smarty;
    }
    Code:
    char *put(SmartArray *smarty, char *str){
        if(smarty == NULL || str == NULL)
            return NULL;
    
    
        if((strlen(str) + getCurrentLength(smarty)) >= smarty->capacity){
            smarty = expandSmartArray(smarty, smarty->capacity*2 + 1);
        }
    
    
        smarty->array[smarty->size] = (char*)malloc(sizeof(char)*(strlen(str) + 1));
    
    
        if(smarty->array[smarty->size] == NULL)
        {
            printf("Couldn't allocate space for new string\n");
            return NULL;
        }
    
    
    
    
        strcpy(smarty->array[smarty->size], str);  //it crashes on this line, but I can't figure out why... It has worked for first 7 lines in my file, and crashes on 8th
    
    
        smarty->size += 1;
    
    
        return smarty->array[smarty->size - 1];
    }
    Output I get is:
    Code:
    Created new SmartArray of size 10
    Expanded SmartArray to size 21
    Expanded SmartArray to size 43
    Expanded SmartArray to size 87
    And then it crashes... It has already passed expanding two times, so that doesn't produce the crash, but I can't see what does. Any ideas?
    Last edited by Rorsh; 06-03-2016 at 10:41 AM.

  6. #6
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Rorsh
    capacity is total number of characters in all strings (basically take all strings in the array and sum their lengths strlen(array[0]) + strlen(array[1]) + ....).
    That is not useful unless you want to do a different approach to allocating memory: allocate space for pointers, then allocate space for one big block, then have the pointers point to within the big block. Only two allocations needed (and it may even be possible to make it one, though with some added complexity, in my opinion). However, if you then want to expand an individual string, things become complicated and possibly quite slow. Also, realloc does not always reallocate in-place, so you would need to handle the possibility that the pointers would no longer point to valid locations, so if your strings are of differing lengths, you might have to save their lengths before re-allocating, so you can restore them afterwards in the new locations.

    If you want to allocate space for each string individually, then it is more useful for capacity to mean "number of pointers for which space has been allocated", i.e., "number of strings that can be stored without having to expand".

    Quote Originally Posted by Rorsh
    Now, if the new capacity is less or equal to the new capacity that means that that array will not be expanded in any way, so I just return, otherwise I tired to copy the contents to the new array (totaly new Struct object), but maybe it would just be better if I incremented value of capacity to new capacity("length" in function declaration)?
    Copying to a new struct object is a waste of time and space. You can modify the current struct object instead.
    Last edited by laserlight; 06-03-2016 at 10:43 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  7. #7
    Registered User
    Join Date
    Nov 2015
    Posts
    10
    Quote Originally Posted by laserlight View Post
    That is not useful unless you want to do a different approach to allocating memory: allocate space for pointers, then allocate space for one big block, then have the pointers point to within the big block. Only two allocations needed
    Can you please elaborate on this a bit more?

    I would allocate
    Code:
    SA->array = (char**)malloc(sizeof(char*)*capacity);  //or what?
    Then this memory is allocated as (char**), how can I insert (char) into it?
    And then do what? How would I append 2nd or third string to that same "big block" of memory allocated?

  8. #8
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Rorsh
    Can you please elaborate on this a bit more?
    Let's not go into this in detail just yet. I suggest that you first work on a solution where capacity means "number of strings that can be stored without having to expand", and then you allocate space for the pointers, and also individually for each string. If you cannot do that, chances are you will run into bigger problems trying to work with just two allocations.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  9. #9
    Registered User
    Join Date
    Nov 2015
    Posts
    10
    Now I have tried to set "size" as current number of strings, and "capacity" as "number of places allocated that can hold strings".

    Code:
    SmartArray *createSmartArray(int length)
    {
        SmartArray* SA = NULL;
    
    
        /*Dynamically allocate space for a new SmartArray.
        Initialize its internal array to be of length length
        or DEFAULT_INIT_LEN, whichever is greater*/
        int size = length;
    
    
        if(size < DEFAULT_INIT_LEN)
            size = DEFAULT_INIT_LEN;
    
    
        SA = (SmartArray*)malloc(sizeof(SmartArray));
    
    
        //if any of the MALLOC's fail, return NULL
        if(SA == NULL)
        {
            printf("Couldn't allocate memory for SmartArray\n");
            return NULL;
        }
    
    
        SA->array = (char**)malloc(sizeof(char*)*size);
    
    
        if(SA->array == NULL)
        {
            printf("Couldn't allocate memory for SA->array\n");
            return NULL;
        }
    
    
    
    
        SA->size = 0;
        SA->capacity = size;
    
    
        printf("Created new SmartArray of size %d\n", size);
    
    
        return SA;
    }
    
    
    
    SmartArray *expandSmartArray(SmartArray *smarty, int length)
    {
        if(length <= smarty->capacity)
            return smarty;
    
    
        smarty->capacity = length;
    
    
        smarty->array = realloc(smarty->array, sizeof(char*)*length);
    
    
        printf("Expanded SmartArray to size %d\n", smarty->capacity);
    
    
        return smarty;
    }
    
    char *put(SmartArray *smarty, char *str)
    {
        if(smarty == NULL || str == NULL)
            return NULL;
    
        if((smarty->size + 1) >= smarty->capacity){
            smarty = expandSmartArray(smarty, smarty->capacity*2 + 1);
        }
    
    
        smarty->array[smarty->size] = (char*)malloc(sizeof(char)*(strlen(str) + 1));
    
    
        if(smarty->array[smarty->size] == NULL)
        {
            printf("Couldn't allocate space for new string\n");
            return NULL;
        }
    
    
        strcpy(smarty->array[smarty->size], str);
    
    
        smarty->size += 1;
    
    
        return smarty->array[smarty->size - 1];
    }
    This seems to work as it should right now. Thank you for all your help!

  10. #10
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,661
    Since your realloc worked without a cast, you can now remove the entirely redundant casts on your malloc calls.

    Also, you should call realloc like so
    Code:
    void *temp = realloc(smarty->array, sizeof(char*)*length);
    if ( temp != NULL ) {
        smarty->array = temp;
    }
    If realloc fails, you still have the original memory allocated. So you need to preserve that pointer until you know the reallocation has succeeded.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 3
    Last Post: 03-14-2013, 03:25 PM
  2. Function pointer and Member function pointer usage?
    By freiza in forum C++ Programming
    Replies: 4
    Last Post: 05-30-2012, 08:17 AM
  3. having some trouble reassigning input
    By ecsx00 in forum C Programming
    Replies: 8
    Last Post: 10-09-2011, 02:08 AM
  4. Reassigning readonly fields
    By Mario F. in forum C# Programming
    Replies: 12
    Last Post: 05-12-2011, 05:43 AM
  5. reassigning FILE pointers & fprintf
    By MPC_Engr in forum C Programming
    Replies: 3
    Last Post: 10-18-2002, 05:24 PM

Tags for this Thread