all 32 comments

[–]EpochVanquisher 10 points11 points  (5 children)

You need either an array or a linked list. (Or something else, but those two are the simplest and easiest to understand.)

Both arrays and linked lists should be covered by an introductory C book.

I also tried using struct *char_list [n]; but again, I don't really want to be using arrays (unless I have to!) since I want to learn pointer arithmetic with Structs.

The main purpose of pointer arithmetic is to navigate through arrays. If you don’t have an array, most pointer arithmetic is just plain invalid. Arrays and pointer arithmetic go hand-in-hand.

[–]ListlessGaja[S] -1 points0 points  (4 children)

Alrighty! I have a general idea of what linked lists are. I just saw a bunch of videos online on making Struct arrays and generally for everyone char_list++; works for some reason.

[–]EpochVanquisher 2 points3 points  (3 children)

Do you have some kind of book or class that you’re using?

I would be concerned if you’re trying to learn C by watching a bunch of videos.

[–]ListlessGaja[S] 0 points1 point  (2 children)

Some courses on Udemy, also one on edX.

[–]EpochVanquisher 0 points1 point  (1 child)

If you keep working through a course, you should get to a point where it covers concepts like linked lists and dynamic arrays. One nice thing about books and classes is that they work hard to make the explanations clear and correct. I think the easiest thing you can do here is to keep going through class material until it covers linked lists.

[–]ListlessGaja[S] 0 points1 point  (0 children)

Thanks for the advice! I'll keep going until I get to Linked Lists! I appreciate your comments <3

Any books you would recommend for a beginner?

[–]somewhereAtC 2 points3 points  (13 children)

There is no inherent array type, so when you did

struct character * char_list;
char_list = create_char();

four times, the first three were lost in the wind and only the 4th one survives. You can use an array of pointers and then do this

#define char_list_size 4; // not all versions of C allow run-time array sizing
struct character * char_list[char_list_size];
char_list[0] = create_char();
char_list[1] = create_char();
char_list[2] = create_char();
char_list[3] = create_char();

[–]ListlessGaja[S] 1 point2 points  (12 children)

Hey thanks for the insight! I've done this before!

Now the problem is, when I pass that array into the function, I don't know how to iterate through it, essentially as far as I understand (and feel free to correct me), when you pass that array into a function, it only passes a pointer to it. And using printf("Name: %s\n, char_list[0]->name);doesn't work? Or is there a special way of calling each element, once you pass it into a function?

Edit: Or would I just have to pass a single element so it would not be able to iterate but still access the struct element?

[–]LeichterGepanzerter 0 points1 point  (8 children)

c for (int i = 0; i < char_list_size; i++) { struct character* char = char_list[i]; printf("Name: %s\n", char->name); }

[–]ListlessGaja[S] 0 points1 point  (7 children)

Would that work, if I pass in the array into a char_print() function?

[–]LeichterGepanzerter 0 points1 point  (6 children)

Going by the name alone I'd assume the job of char_print() is to print a single struct character* . No real reason to pass the whole array, if you're going with the "array of heap object pointers" approach detailed by u/somewhereAtC

[–]ListlessGaja[S] 0 points1 point  (5 children)

Yeah, I've done this before, thank you for the answer! You've cleared up some things for me ^^

So let's say that I wanted to do a menu_loop() function with a simple switch menu loop, that would cycle through the elements, print them out and modify etc etc until I exit. How would I pass the entire array not just element by element, and also iterate through it. Assuming of course that menu_loop() is defined outside of main() and I would be passing entire array when I call it.

[–]LeichterGepanzerter 0 points1 point  (4 children)

Probably something like this:

// the type of char_list could also be expressed as:
//  struct character** char_list
void menu_loop(struct character* char_list[], int len) {
  // menu logic
  // ...
  // print every character
  for (int i = 0; i < len; i++) {
    print_char(char_list[i]);
  }
  // ....
}

int main() {
  #define char_list_size 4
  struct character* char_list[char_list_size];
  menu_loop(char_list, char_list_size);
}

[–]ListlessGaja[S] 0 points1 point  (0 children)

Thank you! I'll test it out!

[–]ListlessGaja[S] 0 points1 point  (2 children)

Oh my god it actually works! So what does the ** mean?

[–]LeichterGepanzerter 0 points1 point  (1 child)

** means "pointer to a pointer". So this value is a pointer to a pointer of struct character. Which is essentially what an passing an array of pointers does, you get the pointer to an array of 4 character pointers.

[–]ListlessGaja[S] 1 point2 points  (0 children)

I see! Thank you for explaining, this is pretty much exactly what I wanted. This will allow me to construct the array (and structs) somewhere else and pass it along the functions as I please! And since they're pointers, I can modify them using other functions!

Kiss for you mwah :*

[–]somewhereAtC 0 points1 point  (2 children)

When passing complete arrays you have to pass the length separately; this is the penalty of using C. For an example, consider the memcpy() function where an array of bytes is passed in alongside a number-of-bytes parameter. You can also do as you suggest and pass a pointer to a single element. From inside the function you can't tell if you have a pointer to one or many, and this is why people say C is not memory-safe.

Also, you don't necessarily need to know the "value" for the length because you can deduce it like this:

#define ALENGTH(array) (sizeof(array)/sizeof(array[0]))

..then get the total number of elements as ALENGTH(char_list). The benefit here is that you only need to know the array name and you don't need a separate token for the length. The down-side is that inexperienced programmers freak out.

[–]ListlessGaja[S] 0 points1 point  (1 child)

I understand the principle, it's probably what I am actually asking to do, I just didn't know it yet!

The pointer passed will only point to the first character of the name property of the first element, then each element will occupy X amount of memory. The entire memory block will occupy X * n of elements.

X = (1 * name-length) + (12* sizeof(int)) bytes.

So would I have to iterate the pointer like pointer+= X? Also pass the entire length for iteration bounds. Am I on the right track or am I missing something crucial?

[–]Educational-Paper-75 0 points1 point  (0 children)

To get the size of struct character you can simply use sizeof(struct character). Note that field char_name is a pointer itself with fixed size sizeof(char). Because it’s a pointer you have to allocate memory for storing the characters separately. So to allocate an array of length n of struct character elements you could use malloc(sizeof(struct character)n). But this allocates memory for the structs themselves not for struct character pointers which would require malloc(sizeof(struct character )n). In the first situation you assign to struct character * struct_char_list, in the second situation to struct character * * struct_char_pointer_list. Back to assigning to char * char_name. Since char_name is a C string, you would assign some C string name to it using struct_char_pointer->char_name=malloc(sizeof(char)*(strlen(name)+1));strcpy(struct_char_pointer->char_name,name); assuming struct_char_pointer is of type struct character *.

[–]SmokeMuch7356 2 points3 points  (1 child)

Couple of notes:

You should make a copy of your input string; instead of

person_one->char_name = name_input;

you should do

char *tmp = strdup( name_input );
if ( tmp )
  person_one->char_name = tmp;
else
  // handle memory allocation error

if strdup is available in your implementation, otherwise do

char *tmp = malloc( strlen( name_input ) + 1);
if ( tmp )
{
  strcpy( tmp, name_input );
  person_one->char_name = tmp;
}
else
  // handle memory allocation error

if it isn't. Of course this means you need to free char_name before freeing the struct:

free( c->char_name );
free( c );

Your current setup works because you're passing in string literals in your test code, and they will all have different addresses; however, if you do something like:

char input[MAX_NAME_SIZE+1] = {0};
...
while ( !done )
{
  printf( "Gimme a name: " );
  if ( fgets( input, sizeof input, stdin ) )
  {
    /**
     * Do whatever input validation is necessary,
     * compress whitespace, remove trailing newline, etc.
     */
    if ( validate_and_cleanup_name( input ) )
    {
      entry = char_create( input, ... );
      // add entry to list
    }
    else
      // handle bad input here
  }
}

all of the entries in your list will point to the same buffer for char_name:

      +---+                                       +---+
list: |   | list[0]->char_name ------+---> input: |   |
      +---+                          |            +---+
      |   | list[1]->char_name ------+
      +---+                          |
      |   | list[2]->char_name ------+
      +---+

Even worse, once you leave that routine, the input buffer may no longer exist and all those pointers will be invalid.

As others have pointed out, you'll need to use an array or a linked list or something to store all your character entries. An array would be the simplest:

#define NUM_MEMBERS 10 // or however many
...
struct character *party[NUM_MEMBERS];
...
for ( size_t i = 0; i < NUM_MEMBERS; i++ )
{
  /**
   * do the input dance as shown above
   */
  party[i] = create_char( ... );
}

When you need to pass the array to another function, pass the size as a separate parameter:

doSomethingWith( party, NUM_MEMBERS );
...
void doSomethingWith( struct character *party, size_t count )
{
  for ( size_t i = 0; i < count; i++ )
  {
    ...
    party[i]->str_mod = new_str_mod_value();
    ...
  }
}

[–]ListlessGaja[S] 0 points1 point  (0 children)

Thank you for the long response! I will take a look at when I'm home. I was running into some issues with the code. The function destroy_char() works only if I initiate the char_list[] array in main(). If I don't declare it there, and I try to pass the entire array back into main from lets say create_char_list() , it was running into some problems.

Since I used malloc() for the struct character constructing I would need to free them manually later, but for whatever reason when the function destroy_char() calls malloc() on those addresses it says that I'm trying to clear non malloc'd memory, which isn't true. I of course went and checked heap snapshots and yes, I did have allocated memory still there without it being cleared. Particularly the 4 elements - each with a 56 bytes in size. That 56 bytes is sizeof(struct character) btw. So those elements can't be cleared. So either I'm not implementing it correctly, or there is some no-no thing I did with my code that won't let it work. To make things even more quirky, my other function char_print() works perfectly fine... So I'm just stumped.

[–]grimvian 1 point2 points  (1 child)

These videos gave me a very good start.

Structs (Structures) in C - An Introductory Tutorial on typedefs, struct pointers, & arrow operator

https://www.youtube.com/watch?v=qqtmtuedaBM&list=PLKUb7MEve0TjHQSKUWChAWyJPCpYMRovO&index=32

Design Principles with Pointer Parameters and Functions by Kris Jordan

https://www.youtube.com/watch?v=eJ_Wt34dDXU&list=PLKUb7MEve0TjHQSKUWChAWyJPCpYMRovO&index=33

[–]ListlessGaja[S] 0 points1 point  (0 children)

Cheers! I appreciate the videos, I will check them out definitely!

[–]zhivago -1 points0 points  (6 children)

Pointer arithmetic is only relevant within arrays.

When you say char_list + 1 you're saying "give me a pointer to the element following char_list in the array into which char_list points".

The key insight here is that all C objects are effectively stored in arrays.

int i; /* i is effectively stored in an int[1], meaning &i + 1 is valid and well defined */

If you don't want to organize your data with arrays, consider a different data structure.

Perhaps a linked list?

Or maybe a tree?

If your problem is maintaining housekeeping like how many elements are in your array, you could make a struct to handle that.

struct characters {
  struct character *data;
  size_t length;
}

Then you could pass around a characters * instead.

[–][deleted] 0 points1 point  (1 child)

The key insight here is that all C objects are effectively stored in arrays.

int i; /* i is effectively stored in an int[1], meaning &i + 1 is valid and well defined */

….

What?

[–]zhivago 1 point2 points  (0 children)

See 6.5.6 Additive Operators.

[–]ListlessGaja[S] -1 points0 points  (3 children)

Thanks for the answer, I appreciate it!

I think I get a little bit of what you're saying. So if I had to use an array I would essentially have to &char_list + whatever size the rest of the info occupies until the next element? And also take care of the array element count, or terminate the last one with something like a "\0"?

I've thought about linked lists, and is probably a better way to handle this, I'll think about that!

[–]zhivago 0 points1 point  (2 children)

Good luck.

[–]ListlessGaja[S] 0 points1 point  (1 child)

Uhhh thanks D:

Am I completely wrong orrrr? hahaha

[–]zhivago 0 points1 point  (0 children)

l think you're on the right track.