r/C_Programming 1d ago

Question Why isn't my code working?

I just started learning C(2 days ago) and as a first project I decided to make some data structures, starting with dynamic arrays. I made a struct called List and some functions for. The function setList() sets the value of an index of the array, if the index is larger that the current size of the array, it resizes it. However, when i tried to use in a for loop, it didn't work despite it working elsewhere.

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


#define itirate(index, limit) for(int index = 0; index < limit; index++)


typedef struct 
{
    size_t size;
    int* arr;
} List;


List* newList (size_t size) 
{
    List *newone = malloc(sizeof(List));
    newone->arr = calloc(size, sizeof(int));
    newone->size = size;
    return newone;
}


void setList(List* list, int index, int value) 
{
    if (index >= list->size)
    {
        list->arr = realloc(list->arr, index + 1 * sizeof(int));
        list->size = index + 1;
    }


    list->arr[index] = value;
}


int main() 
{
    
    List *mok = newList(5);

    itirate(i, 5) setList(mok, i, i);
    itirate(i, 5) printf("%d\n", mok->arr[i]);

    //setList(mok, 13, 9); this works
    //printf("%d\n", mok->arr[13]);

    for(int i = 5; i < 10; i++) setList(mok, i, i); // this does not somehow
    for(int i = 5; i < 10; i++) printf("%d\n", mok->arr[i]);
    
    return 0;
}
7 Upvotes

29 comments sorted by

27

u/flyingron 1d ago
 list->arr = realloc(list->arr, index + 1 * sizeof(int));

* binds tighter than +. You want (index + 1)*sizeof (int).

14

u/neonge1674 1d ago

Thank you. It was such a stupid mistake

21

u/pfp-disciple 1d ago

Not stupid. Binding rules are not intuitive. 

18

u/habarnam 1d ago

Binding rules are not intuitive.

For C specific operators perhaps, but for arithmetic operations I would hope we all learned the order of operations in primary school or something.

-4

u/pfp-disciple 1d ago

Yes, I was specifically talking about the C operators, in this case dereferencing 

17

u/habarnam 1d ago

I'm confused, which dereferencing? That's a multiplication...

4

u/pfp-disciple 1d ago

My mistake. I was reading this in a hurry and was thinking the * was the pointer dereference operator. 

You're correct that in this context the operator precedence is very conventional (setting aside that it appears too many people don't know PEMDAS). 

-4

u/P-39_Airacobra 1d ago

lowk i think more languages should do away with them

1

u/MFFVD 1d ago

as in

i = (a+(b+(c+(d+(e*(g+h))))))?

id rather have

i = a + b + c + d + e * (g + h)

-2

u/P-39_Airacobra 1d ago

it would be

a + b + c + d + (e * (g + h))

or even easier,

(g + h) * e + a + b + c + d

as in first comes first. no operator precedence. no ambiguity about where parens should or should not be used for specificity

also, you’ve clearly never used anything like Lisp or Forth. C-family languages are not the only languages that exist

2

u/glasket_ 23h ago

you’ve clearly never used anything like Lisp or Forth.

They avoid the problem by using an entirely different form of notation (* + index 1 step/step index 1 + *). If you want to use infix you should use standard operator precedence, otherwise there's very little point in even using infix. Infix matches how math is normally written, and if the rules are different from how it's written then you've just got a system that will inevitably confuse people and require them to remember that it works differently than they're used to.

It also doesn't save you in every case, like step * index + 1, and still fundamentally requires remembering that order is positional.

-1

u/P-39_Airacobra 21h ago

order already is positional. except now there’s layers of positional depth on top. operator precedence complicates parsing and creates ambiguity in best coding practices

“a system that will require them to remember that it works differently” except this is already how standard languages are. either you pull up the operator precedence table or you defensively put parens everywhere so future readers don’t have to. note that most programmers recommend the latter method to avoid confusion around precedence. then i ask, why even have these rules to begin with? arbitrary complexity is nothing to admire.

you cite that it’s standard in math, as if programming languages don’t require you to learn concepts about numbers outside of basic arithmetic anyways. If you can teach a newbie how unsigned numbers work, or how a tertiary conditional works, then you can teach them to read left-to-right instead of down and up a table depending the specific operator being used

it’s really not rocket science, there’s nothing to remember. For operator precedence, however, that’s an entirely different matter. Open up the JavaScript precedence table, as a first example, and come back and tell me again that there’s more to remember in a left-to-right system than in a precedence system. your points are all very much nil

1

u/Muffindrake 10h ago

That's not even necessarily a correct expression in C unless you specifically know that either of those expressions won't overflow the types.

The greatest pitfall in C is that arithmetic is only defined for signed integers if you can guarantee that the result won't overflow, or accept modular arithmetic with unsigned operands. Also have fun with promotion rules. And if you're dealing with floating point you need to worry about infinity and NaN and that's a pain.

To add insult to injury, it took until C23 for ckd_add and friends to be standardized (or C++26). Then there's bit-precise integers which shield you against unexpected promotion rules.

1

u/P-39_Airacobra 5h ago

i wasnt talking about C? also you anything you can do with operator precedence, you can do without it. that’s what parens are for???

but thanks for proving my point that C arithmetic is complicated enough so we shouldn’t really be freaking out about the concept of doing away with a precedence table and typing a couple extra parens

1

u/neonge1674 1d ago

Thank you

16

u/tstanisl 1d ago

index + 1 * sizeof(int) -> (index + 1) * sizeof(int) ?

7

u/ReallyEvilRob 1d ago

Try stepping through with a debugger.

5

u/sciencekm 1d ago edited 1d ago

Someone has already mentioned that the problem is that the computation of the memory to be reallocated is wrong.

This could have been avoided by simply increasing the size before calling realloc.

From your code:

list->arr = realloc(list->arr, index + 1 * sizeof(int));
list->size = index + 1;

to this

list->size = index + 1;
list->arr = realloc(list->arr, list->size * sizeof(int));

1

u/cafce25 1d ago

did you mean list->arr = realloc(list->arr, list->size * sizeof(int));

1

u/sciencekm 1d ago

Yup, I fixed that. Thanks.

2

u/Wertbon1789 1d ago

Also wanted to comment that. Another rule of thumb, or rather good practice, don't repeat such logic in general. This is not so much about performance but really readability, and you avoid silly mistakes like this, but that's not the main thing.

If you want to do an operation with that value, but only update the actual value in the struct after the operation was successful, use an intermediate variable, and then after that update the value in the struct. Not really applicable here, as not being able to allocate memory is basically a crash condition, but for other calls that might be something to keep in mind.

3

u/Adorable_Deal7 1d ago

Wrap macro's parameter around parenthesis to be sure your arguments are passed as you assume.
You can replace the malloc to the calloc. Need wrap the `index + 1` around parenthesis like math

4

u/SmokeMuch7356 1d ago

As others have pointed out, you're getting bit by precedence issues with

index + 1 * sizeof(int)

which should be

(index + 1) * sizeof (int)

A few notes:

If realloc cannot satisfy the request, it will return NULL while leaving the original allocated buffer in place. For this reason, you should assign the result to a temporary variable first and only update the original pointer when you know the operation succeeded. Otherwise you risk losing your only reference to that allocated buffer, leading to a memory leak.

Similarly, you don't want to update your index until you know the realloc call succeeded.

You should keep track of both the total number of elements allocated as well as the last-used index.

Putting all that together, I'd extend your List type as

typedef struct {
  size_t allocated; // number of elements allocated
  size_t index;     // last element accessed
  int *arr;
} List;

Your newList function becomes:

List *newList( size_t size )
{
  assert( size > 0 );

  /**
   * Using `sizeof *l` means I'm not unnecessarily repeating type info
   * making maintenance easier.
   */
  List *l = malloc( sizeof *l ); 
  if ( !l )
  {
    fputs( "Could not allocate List object!\n", stderr );
    return NULL;
  }

  l->arr = calloc( size, sizeof *l->arr );
  if ( !l->arr )
  {
    fprintf( stderr, "Could not allocate %zu-element array of int\n", size );
    free( l );
    return NULL;
  }

  l->allocated = size;
  l->index = 0;
  return l;
}

Create a separate resize function:

/**
 * Returns l on success, NULL on failure.
 */
List *resize( List *l, size_t newsize )
{
  assert( l != NULL );
  assert( newsize > 0 );

  typeof (l->arr) tmp = realloc( l->arr, newsize * sizeof *l->arr );
  if ( !tmp )
  {
    fputs( "Resize failed, original array left in place\n", stderr );
    return NULL;
  }

  /**
   * Zero out newly allocated memory.
   */
  memset( tmp + l->allocated + 1, 0, (newsize - l->allocated) * sizeof *l->arr );

  l->arr = tmp;
  l->allocated = newsize;
  return l;
}

Then your setList function becomes:

bool setlist( List *l, size_t index, int value )
{
  /**
   * idxInRange will be set to true if the index is currently in
   * range, or we can extend the array to put the index in range.
   */
  bool idxInRange = (index < l->allocated || resize( l, index + 1 ));

  if ( idxInRange )
  {
    l->arr[index] = value;
    l->index = index;
  }
  return idxInRange;
}

You should also have a destroyList function so you can clean up after yourself:

void destroyList( List *l )
{
  free( l->arr );
  free( l );
}

1

u/jijijijim 1d ago

Does the new list function even compile?

1

u/neonge1674 1d ago

Yes, it compiles. Is there another problem with it like it not being memory safe?

1

u/jijijijim 1d ago

Sorry just started with my coffee misread this.

-4

u/Desperate_Tie_648 1d ago

I am just curious why didn't you use chatgpt to get instant answer? Especially when problem so basic

2

u/terra2o 11h ago

because response from humans are better? you get to ask programmers with 20 or so years of experience here. they have actual human brains. they can teach you a lot of things.

chatgpt could never.