-1

So I've been looking at structures, functions and pointers for days now. I just cant wrap my head around structures good enough to do what I want...

I was trying to write a function, which was originally going to receive user input (taken with fgets) as an argument. I have put that aside now, and just decided to give the function a single argument. That argument will be the name of a struct, and I'll use that name to access it's variables and print them the way I want.

typedef struct
{
 int hp;
 char *name;
} bare;
bare example;
void print_info(char *name);
int main()
{
 example.hp = 5;
 strcpy(example.name,"John");
 print_info("example");
}
void print_info(char *name)
{
 printf("The hp of %s is %d", (*name), (*name)->hp);
}


Whatever bloody thing I put there instead of char *name, it always ended up giving me the error "error: struct or union expected"! I tried struct bare **name and (*name)->hp/(*name).hp, char *name/**name and *&name.hp, *&name->hp, every possible solution I could think of..! i think they all turned out to be nonsense... I just cant wrap my head around pointers and structs enough to do this! A little help please? I searched high and low on function arguments, pointers and structs, yet couldn't find a solution/question like mine..

steadybit
  • 1
  • 1
  • A pointer variable will point to the memory that you want to use to store the name. An array is the memory that can be used to store the name. – jxh Sep 28 '21 at 18:43
  • The only way to refer to an object from a string is to look up the object from some association table. If you export your symbols from translation unit so that it is findable by a linked library, you can do the lookup using dlsym. Otherwise, you have to create the lookup data structure yourself. That data structure would allow you to associate a string to a `bare *`. – jxh Sep 28 '21 at 19:04
  • So, your `print_info` function would need to lookup the `bare *`, and then you can use `->` on the `bare *`. – jxh Sep 28 '21 at 19:06

3 Answers3

1

First, it's better to declare your struct this way:

typedef struct bare {
    int hp;
    char *name;
} bare;

Second, avoid global variable as much as you can. I don't see the point of declaring example in the global namespace since you are using it only inside main().

Third, this line has a problem:

strcpy(example.name, "John");

You are attempting to copy "John" to an uninitialized pointer (example.name) that points to some random memory address. You have to either allocate enough space using malloc() (and free it when you're done with it), or use a fixed-length array. Moreover, it's better to use strncpy() because it allows to specify the maximum number of characters to copy. This way you avoid the risk of buffer overflow.

Fourth, to avoid copying your entire struct to print_info() (in fact, any other struct to any other function), you should pass its address.

With all that said, here is how your code should be written:

#include <stdio.h>
#include <string.h>

typedef struct bare {
    int hp;
    char name[100]; // Make sure it has enough space, or use malloc() if you don't know how much it will hold initially
} bare;

void print_info(bare *name);

int main(void)
{
    bare example; // Declare it inside main()
    example.hp = 5;
    
    strncpy(example.name, "John", sizeof example.name); // This works and is safe
    
    print_info(&example);
}

void print_info(bare *name)
{
    printf("The hp of %s is %d", name->name, name->hp);
}

Output:

The hp of John is 5
Zakk
  • 1,935
  • 1
  • 6
  • 17
0

I think what you wish to do is this:

#include <stdio.h>
#include <string.h>
typedef struct {
    int hp;
    char *name;
} bare;
bare example;
void print_info(bare *name);
int main() {
    example.hp = 5;
    strcpy(example.name, "John");
    print_info(&example);
}
void print_info(bare *name) {
    printf("The hp of %s is %d", name->name, name->hp);
}

Or if you want to pass example by value:

#include <stdio.h>
#include <string.h>
typedef struct {
    int hp;
    char *name;
} bare;
bare example;
void print_info(bare name);
int main() {
    example.hp = 5;
    strcpy(example.name, "John");
    print_info(example);
}
void print_info(bare name) {
    printf("The hp of %s is %d", name.name, name.hp);
}

Why did your code not work?

  1. print_info had an incorrect argument data type. What you wanted was to pass an object of bare or perhaps a pointer to an object of bare, but you were instead passing a variable of type char *.
  2. The arrow operator is used on pointers. Maybe take a look at Arrow operator (->) usage in C.
0

You wanted to pass in a string typed in by the user.

I was trying to write a function, which was originally going to receive user input (taken with fgets) as an argument. I have put that aside now, and just decided to give the function a single argument.

This explains why you pass in a char * to your function. The input value was originally going to be read from fgets. In your program, you passed in the name of your variable.

bare example;
/* ... */
 print_info("example");

To do a dynamic lookup on a symbol name, use dlsym.

As I suggested in comments, if you want to be able to look up the name of a variable to find the associated object, you can use dlsym so long as you are on a POSIX system (like Linux). For example:

// Need to inlcude <dlfcn.h> and link with -ldl
// Make local variables findable with -rdynamic
void print_info(char *name)
{
 bare *p = dlsym(0, name);
 if (p != NULL)
  printf("The hp of %s is %d", p->name, p->hp);
 else
  printf("%s not found!\n", name);
}

So long as you include <dlfcn.h> and use -ldl when linking the program, and you make your symbol table visible (with -rdynamic on GCC), the program will find the pointer to your example variable. (Try it online!)

But you probably meant to do a lookup by name.

However, you seemed to have mixed some things up. Usually, the user will not care what names you have used for the variables in your program. You would never expect fgets to give you "example" because that is not what the user would type in.

You probably meant to search for the bare record that matches the name parameter of bare. In your case, "John".

 print_info("John");

Normally, you would have a table of bares that you would look over and check for a match. However, in your simplified example, there is only one to check.

bare * find_bare(char *name)
{
 if (strcmp(name, example.name) == 0) return &example;
 return NULL;
}

void print_info(char *name)
{
 bare *p = find_bare(name);
 if (p != NULL)
  printf("The hp of %s is %d", p->name, p->hp);
 else
  printf("%s not found!\n", name);
}

It isn't hard to create and search a table of bare.

In this case, you could probably simple create an array of bare to represent your collection that you would search over.

#define BARE_TABLE_SIZE 50
bare table_example[BARE_TABLE_SIZE];

Assuming you add the code to populate your table, you could use a simple loop to search for a matching name.

bare * find_bare(char *name)
{
 for (int i = 0; i < BARE_TABLE_SIZE; ++i)
 {
  if (strcmp(name, table_example[i].name) == 0)
   return &table_example[i];
 }
 return NULL;
}

Your example.name was an uninitialized pointer.

Finally, the most egregious error in your program is the attempt to call strcpy on an uninitialized pointer. One solution is to allocate new memory to hold the new name and assign the location of the new name to the pointer. POSIX systems (like Linux) supply a function called strdup that creates a copy of the input for you, in newly allocated memory.

 example.name = strdup("John");

Since the memory is allocated by malloc, you would need to call free on the pointer if example is ever recycled for a new name.

jxh
  • 69,070
  • 8
  • 110
  • 193
  • Yes thank you! I came from C# and a bit of Python, I'm really not used to compiler options, linking, allocating memory for variables and whatnot (couldnt compile your code). I wanted to take the string, (optionally look if it matches any struct variables first) refer to a struct and print a member of that struct! You did what I wanted to.. sort of, just not as flexible as I wanted it to be, I have lots of structs in my project, so I wanted to search every single one under bare and look for a match.. If I do ```example.name;```first,then assign something to it, do I still need to use malloc() ? – steadybit Sep 29 '21 at 15:15
  • Coming from C#, you may get closer to the coding style you are used to if you use C++ rather than C. – jxh Sep 29 '21 at 17:27
  • If you do not want to deal with `malloc` and `free` of the name, define it as an array, as suggested in the other answers. – jxh Sep 29 '21 at 17:30