Extending arrays in C using pointers

Viewed 173

I'm learning pointers in C. To get into details, I'm working on an array program. What I'm trying to achieve is, if the array is of size 0(initially) create a dynamic array using malloc of size 2 and then add the element in the initial position.

Furthermore, when more elements are added and if the total elements == size of the array, I create a new array using malloc with twice the previous size. I create a temporary array, copy elements from the previous array, then add the new element in the end. The problem is when I try to print the elements I get the address of the last element instead of the element.

Here's the full code.

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

void display(int *arr, int *total_elements)
{
    if(*total_elements==0){
        printf("No elements present in array\n");
        return;
    }
    else
    {
        for(int i=0;i< *total_elements;i++){
            printf("%d\t",*(arr+i));
        }
    }
}

void push(int *arr, int *size, int ele, int *total_elements)
{
    if(*size==0)
    {
        *arr = (int*)malloc(2*sizeof(int));
        arr[*size] = ele;
        *total_elements = *total_elements + 1;
        *size = *size + 2;
        return;
    }
    else if(*size>*total_elements)
    {
        arr[*total_elements] = ele;
        *total_elements = *total_elements + 1;
        return;
    }
    else if(*size==*total_elements)
    {
        int *temp,i;
        temp = (int*)malloc((*(size)*2)*sizeof(int));
        for(i=0;i < *size;i++)
        {
            temp[i] = *(arr+i);
        }
        temp[i]=ele;
        arr = temp;
        *size = (*size)*2;
        *total_elements = *total_elements + 1;
        for(int i=0;i< *total_elements;i++){
            printf("%d\t",*(arr+i));
        }
    }
}

void pop()
{
}

void main()
{
    int select=0;
    int *arr, size=0, total_elements = 0;
    while(select!=4)
    {
        printf("\nSelect a number\n1. push\n2. pop\n3. display\n4. exit\n");
        scanf("%d",&select);
        switch (select)
        {
            case 1: 
                printf("Enter element to push: ");
                int ele;
                scanf("%d",&ele);
                push(arr,&size,ele,&total_elements);
                break;
            
            case 3: 
                display(arr, &total_elements);
                break;

            case 4: break;
            
            default:
                break;
        }

    }
}

After entering 3 elements(11,22,33). The print output from the push function.

11      22      33

The print output from the display function.

11      22      -1943936495

I'm sure that there is a mistake in the push function while using pointers. Why is this causing?

2 Answers

The push function works correctly because of arr = temp;. Observe that arr is actually a local variable inside the push function. When the push function returns, the main does not have any idea that the arr variable was ever changed in push.

When you pass arr to display, the copy that main has is sent. If you need to update arr inside another function (push in this case), you need to pass the address of arr - push(&arr,&size,ele,&total_elements); Likewise, the push function should now accept int **arr instead of int *arr. Also, make sure you dereference arr the right number of times inside push.

Inside push, you can update arr by doing *arr = temp;. After this, arr will point to the new array that was malloced in push and you can pass arr to your display function.

In your code:

  1. arr is the array pointer.
  2. size is the current number of filled/used elements.
  3. total_elements is the number of allocated elements.

You are overallocating the arr (using total_elements) which is good.

But, you are conflating use of size and total_elements. In display, you should use size.

Note that, in main, you are not initializing arr [to NULL]. For your code, this is okay because of the way push is implemented. But, it's generally good practice to set it to NULL--see below.

In push, you're not updating arr for the caller.

Also, push is more complicated than it needs to be (i.e.) too many special cases.

If main initialized arr, then push could just use realloc (as realloc handles a null pointer as doing a malloc [internally]).

Instead of passing 3 things to push, we can create a control struct for the array that contains your separate scalar values. This greatly simplifies the code. We just pass around the [single] struct pointer to all the functions.


Here is some refactored code. For testing purposes, I added a case 5:

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

typedef struct {
    int *arr;                           // pointer to array data
    int size;                           // number of filled/inuse elements
    int total_elements;                 // number of allocated elements
} dynarr_t;

void
display(dynarr_t *dyn)
{

    if (dyn->size == 0) {
        printf("No elements present in array\n");
        return;
    }

    int totlen = 0;

    for (int i = 0;  i < dyn->size;  i++) {
        if (totlen >= 68) {
            printf("\n");
            totlen = 0;
        }
        int curlen = printf(" %d",dyn->arr[i]);
        totlen += curlen;
    }

    if (totlen > 0)
        printf("\n");
}

void
push(dynarr_t *dyn,int ele)
{

    // grow the array
    if (dyn->size >= dyn->total_elements) {
        // the amount here is arbitrary
        dyn->total_elements += 10;

        dyn->arr = realloc(dyn->arr,sizeof(*dyn->arr) * dyn->total_elements);

        // out of memory
        if (dyn->arr == NULL) {
            perror("push");
            exit(1);
        }
    }

    // append new element
    dyn->arr[dyn->size++] = ele;
}

// RETURNS: 1=valid, 0=empty
int
pop(dynarr_t *dyn,int *ele)
{

    // no elements in array
    if (dyn->size <= 0)
        return 0;

    // pop the last element
    *ele = dyn->arr[--dyn->size];

    return 1;
}

int
main(void)
{
    int select = 0;
    int ele;

    // allocate a pointer to the array control struct
    dynarr_t *dyn = calloc(1,sizeof(*dyn));

    while (select != 4) {
        printf("\nSelect a number\n1. push\n2. pop\n3. display\n4. exit\n");
        scanf("%d", &select);
        switch (select) {
        case 1:
            printf("Enter element to push: ");

            scanf("%d", &ele);
            push(dyn, ele);
            break;

        case 2:
            if (pop(dyn,&ele))
                printf("main: pop %d\n",ele);
            else
                printf("main: pop empty\n");
            break;

        case 3:
            display(dyn);
            break;

        case 4:
            break;

        case 5:  // internal test
            for (int ele = 1;  ele <= 100;  ++ele)
                push(dyn,ele);
            display(dyn);
            break;

        default:
            break;
        }
    }

    // free up the memory
    free(dyn->arr);
    free(dyn);

    return 0;
}

Here is the test output [for 5 4]:


Select a number
1. push
2. pop
3. display
4. exit
 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
 96 97 98 99 100

Select a number
1. push
2. pop
3. display
4. exit
Related