Expandable integer List in C I just started learning C fundamentals and started to write a List that is limited to integers for the beginning and has dynamic memory allocation according to its content.

I've written custom simple assertion functions that revealed, that upon reallocating less memory when shrinking the list, it is still possible to access the removed element - my guess is that there's no need to move the pointer when shrinking its allocation and realloc() does not free the memory? Is it right? Is this code C-ish? Thanks for your reviews!

list.h

typedef struct {
    long size;
    int* elements;
} intlist;

/**
 * Creates an empty list
 */
intlist il_create();

/**
 * Appends a value to a list and returns its index
 *
 * @return index of the item
 */
long il_push(intlist*, int);

/**
 * Retrieves a value by its index
 *
 * @return item of the list
 */
int il_get(intlist*, long);

/**
 * Removes the last element
 */
void il_pop(intlist*);


main.c

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include "list.h"

void assert(char* msg, long actual, char expression) {
  static int assert_nr = 0;
  ++assert_nr;

  if(!expression) {
    fprintf(stderr, "[ERROR] Assertion %d failed\n", assert_nr);
    fprintf(stderr, "[ERROR] Expected: %s\n", msg);
    fprintf(stderr, "[ERROR] Actual:   %ld\n", actual);
  }
}

void assertEquals(char *msg, long actual, long expected) {
  assert(msg, actual, actual == expected);
}

void assertNotEquals(char *msg, long actual, long expected) {
  assert(msg, actual, actual != expected);
}



int main(int argc, char const *argv[]) {
  // Create an empty list
  intlist list = il_create();

  { // List is initially empty
    assertEquals("Size is 0", list.size, 0);
  }

  { // The item is added to the list
    int index = il_push(&amp;list, 123);
    int item = il_get(&amp;list, 0);

    assertEquals("Size is 1", list.size,  1);
    assertEquals("First index is 0", index, 0);
    assertEquals("First element is 123", item, 123);
  }

  { // Another item is added to the list
    int index = il_push(&amp;list, 456);
    int item2 = il_get(&amp;list, 1);

    assertEquals("Size is 2", list.size, 2);
    assertEquals("Second index is 1", index, 1);
    assertEquals("Second element is 456", item2, 456);
  }

  { // Last element is removed and thus last = first
    il_pop(&amp;list);

    assertEquals("Size is 1", list.size, 1);

    int first = il_get(&amp;list, 0);
    int last = il_get(&amp;list, list.size-1);

    assertEquals("First item is 123", first, 123);
    assertEquals("Last element is the first one", last, first);

    int deleted = il_get(&amp;list, 1);
    assertNotEquals("Deleted item is not 456", deleted, deleted != 456);
  }

  { // Removed element is overwritten when pushing a new one
    int index = il_push(&amp;list, 789);
    int item = il_get(&amp;list, index);

    assertEquals("Index is 1", index, 1);
    assertEquals("Element is 789", item, 789);
  }

  return 0;
}


intlist il_create() {
  return (intlist){.size=0};
}

long il_push(intlist* list, int value) {
  int index = list-&gt;size;

  list-&gt;elements = realloc(list-&gt;elements, ++list-&gt;size * sizeof(int));
  *(list-&gt;elements + index) = value;

  return index;
}

int il_get(intlist* list, long index) {
  return *(list-&gt;elements + index);
}

void il_pop(intlist* list) {
  list-&gt;size -= 1;
  *(list-&gt;elements + list-&gt;size) = 0;
  list-&gt;elements = realloc(list-&gt;elements, list-&gt;size * sizeof(int));
}
It does. You directly assign list-&gt;elements to the result of realloc which can return NULL, in which case your original array is leaked (but it's okay since you immediately assign a value to a NULL-based array after that, which is UB).

Few more observations.


The list itself can only be operated via its public interface that consists of methods that accept list object by pointer. Perhaps it makes sense to return by pointer from constructor, too; then you can make list's implementation an opaque struct, all its fields will be private.
You should define a destructor to release the array, that otherwise (currently) leaks.
Your current allocation strategy is inefficient. Reallocating the whole array per each new element raises push's complexity to O(N), so creating an array of N elements takes O(N²) time. A traditional approach to deal with it is adding capacity to the list object; capacity equals to the size of allocated space and grows exponentially: when list's size reaches capacity, on next element push capacity doubles, thus reducing push complexity to ammortized O(1).
Elements are stored in a contiguous array, perhaps a better descriptive name? Traditionally, list is associated with linked lists in C family.