1

My requirement is to add data of different datatypes into dynamically allocated structure memory.

When I use scanf(), it is not taking inputs properly, even I included %*c to take up the new line character, '\n', included in the input buffer. When the user enters spaces in between his first input, the scanf() is not taking the second argument, age as input.

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

typedef struct node{
    char name[15];
    int age;
}details;

void entry(details*,int);

int main()
{
    int n,i;
    char num;
    printf("Enter number of details\n");
    scanf("%d%*c",&n);
    details* ptr = (details*)malloc(sizeof(details));
    entry(ptr,n);
    for(i=0;i<n;i++)
        printf("\n%s\n%d\n\n",(ptr+i)->name,(ptr+i)->age);
    return 0;
}

void entry (details *det,int n)
{
    int i=0;
    for(i=0;i<n;i++){
        printf("Enter name:");
        scanf("%s%*c",&(det+i)->name);
        printf("Enter Age:");
        scanf("%d%*c",&(det+i)->age);
    }

}

How to use gets() to take integer input and use it in dynamically allocated memory. I even tried using atoi() to convert char to int as well. Then also the program is not working properly.

I need to dynamically allocate structure memory and add data of different data types into it.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
IndyRose
  • 37
  • 7
  • Aside: There is no need to cast the return value of `malloc()`. `malloc()` and family return a generic void pointer that is implicitly converted to any other pointer type. Casting the result merely adds clutter to the code. You should also check the return value of `malloc()` and compare it with `NULL` to see if it failed, or invoke undefined behavior with a subsequent `NULL` pointer dereference. – Harith Aug 28 '23 at 14:57
  • Your format strings are have two specifiers but you only pass one arg. For some ideas on how to prompt a user [using `fgets`], see my answer: [Check if all values entered into char array are numerical](https://stackoverflow.com/a/65013419/5382650). Note that `&det[i].name` is more canonical. Or, increment the pointer: `for (i = 0; i < n; ++i, ++det) { printf(...); scanf(...,&det->name); }` The `malloc` only allocates space for one element and needs to be: `details* ptr = malloc(sizeof(details) * n);` – Craig Estey Aug 28 '23 at 14:58
  • 1
    Instead of `(det + i)` why not `det[i]`? You can also increment the `det` pointer itself which simplifies access considerably. – tadman Aug 28 '23 at 14:59
  • @Craig The `*` format specifier scans but discards input. From [cppreference](https://en.cppreference.com/w/c/io/fscanf): assignment-suppressing character *. If this option is present, the function does not assign the result of the conversion to any receiving argument. – Adrian Mole Aug 28 '23 at 15:06
  • @Harith Indeed. But a far more serious problem in that `malloc` line is that the value of `n` is not being used as a multiplier for the allocation size. – Adrian Mole Aug 28 '23 at 15:07
  • @Harith: So you are saying that I can directly assign it to Details* ptr ? – IndyRose Aug 28 '23 at 15:07
  • @tadman it will throw an error of "Invalid type argument ->" – IndyRose Aug 28 '23 at 15:13
  • If you increment the pointer, you can use `&(det->name)` etc. As easy as `i++,det++` in your loop. – tadman Aug 28 '23 at 15:13
  • 1
    @tadman Not `&(det->name)` for the string, just `det->name`. – Adrian Mole Aug 28 '23 at 15:14
  • Also worth noting that you can move your definition of `entry` above `main` and it'll eliminate the need for that pre-declaration. Your declaration with just types is valid, yet it'll be frustrating should someone else encounter it as they now have no idea what those arguments are. If you need to declare, include the argument names so there's some idea of how to call it correctly. – tadman Aug 28 '23 at 15:15
  • @AdrianMole Right, of course. – tadman Aug 28 '23 at 15:15
  • Yes, you can simply write `details *ptr = malloc (...)`. And as @AdrianMole above mentioned, you ought to use `n` as a multiplier for the allocation size. Currently, you are only allocating space for a single `struct`, but require space for `n` `struct`s. – Harith Aug 28 '23 at 15:16
  • A) In C you do not need to cast the result of `malloc()`. B) You may want to use `calloc()` as it forces you to talk through your thinking of how much memory you need, splitting out size vs. count explicitly. As a bonus it's also pre-zeroed, which can help if some fields are optional. – tadman Aug 28 '23 at 15:16
  • `(ptr+i)->name` is normally written as `ptr[i].name`. – Andrew Henle Aug 28 '23 at 15:26
  • @tadman Kindly modify the code and show me the how to get structure members with multiple inputs on a dynamically allocated memory with spaces in between the input of the name struct member please – IndyRose Aug 28 '23 at 15:27
  • @IndyRose Stackoverflow is not a free-coding service or a tutorial site. I am afraid to say that one can't modify the code and show you what you demand. I suggest you start with a good C book instead and work your way up. – Harith Aug 28 '23 at 15:30

2 Answers2

2

The code as posted has many errors. You can fix all them --- and even get the code to work this way --- but is not a good program.

Also I think that you are expecting some things that are not true.

About the questions and comments

My requirement is to add data of different datatypes into dynamically allocated structure memory

Well, to add data to structures you use assignment. Makes no difference if the struct is static or dynamically allocated.

When I use scanf(), it is not taking inputs properly, even I included %*c to take up the new line character, '\n'

Do you have a text book? A manual? A reference site you believe in?

scanf() is from the '70s and you can be sure it takes inputs properly.

  • Do you know that scanf() returns an int? What about test it just to see if something was read, instead of just blindly going ahead?
  • The *%c will not "take up" a \n. The newline is the one that makes scanf() return. It is your program that has to consume the data properly
  • scanf() was written to consume tabular data, like csv files with 1000's of lines of values, possibly separated by, well, commas --- as csv in Comma Separated Values. It was never meant to be used to read input data from the keyboard. At least not until instructors and authors started to teach using scanf() this way. A bad move, I believe.

When the user enters spaces in between his first input, the scanf() is not taking the second argument, age as input.

Yes! By design scanf() skips white space --- spaces, tabs and newlines --- in the search for data to fill data as in the specifiers, those things that starts with % and not with %%.

How to use gets() to take integer input and use it in dynamically allocated memory

Well, do use. gets() is obsolete and could not even compile. Use fgets().

I even tried using atoi() to convert char to int as well

This always work, but with const char* not char. Read the manual.

I need to dynamically allocate structure memory and add data of different data types into it

These are unrelated stuff.

about the code as posted

This is your definition of the data your program works it:

typedef struct node{
    char name[15];
    int age;
}details;

Why node? It is not used. Not mentioned anywhere. If it is a node is a node of what? Some kind of data structure? A linked list? An array? A map? A queue? A tree?

But there is no such thing in the program yet...

In main you have this

    details* ptr = (details*)malloc(sizeof(details));
    entry(ptr,n);

So one can think that your data sctructure is a dynamically allocated array of n values...

If so, just write this. It is easier for you. It is called encapsulation and it is very simple, yet powerful.

See an example:

typedef struct
{
    char name[15];
    int  age;
} Details;

typedef struct
{
    unsigned cap; //capacity
    unsigned size; // size now
    Details* data; // and the data
} My_data;

So your unit of work is My_data. At any time you have size items and, sure, it is unsigned: makes not much sense have a size of -42 for example.

  • Do not write interactive code for a program that is still not working.
  • Do not mix data entry with logic.

A C example from the start

I will write a program like this one now, here, step by step, since this is a fairly common example, posted with fairly common mistakes, and a complete code can be useful to others. **

EDIT: It is a giant post due to the copy and paste and interactive way of writing. Sorry for that: No TL;DR, just DR

how to create My_data

Sure, there are countless ways. But look this one:

My_data* so_create(unsigned capacity)
{
    if (capacity == 0) return NULL;  // error
    My_data* cnt = (My_data*)malloc(sizeof(My_data));
    if (cnt == NULL) return NULL;  // error
    cnt->size = 0;                 // sure it is empty
    cnt->cap  = capacity;
    cnt->arr = (int*)malloc(capacity * sizeof(int));
    if (cnt->arr == NULL)
    {  // malloc for the array failed
        free(cnt);
        return NULL;
    };
    return cnt;  // ok
}

Just return a pointer to one My_data structure with the expected capacity, but empty. Or return NULL for an error. Simple or not?

The size is inside, the capacity is inside, the data is inside.

        one = so_create(400);

The line above returns a pointer to such struct, empty but capable of holding 400 Detail data.

[off topic]

Many people will tell you to not cast the pointer returned by mallloc(). It is like a cult. But a cult from the 80's and 90's based on a book from '95 and a document called C FAQ. And even in these book and document are said that it may be such not an absolute fact.

A funny example from the C_FAQ: one reason for never cast the pointer is to be able to get a compiler error if the user forgets to include stdlib.h in the code. If you do that your problem is bigger than a cast.

For the compiler it makes no difference. But for you, reading your code or, worst, reading code of others, this (cast the result) can be a life saver.

No surprise, this document was never updated, this book had no revisions, and as time passed --- decades --- many people saw that implicit things are not so good after all. Even things like auto in C++ are not welcome in some circles, and authors like Scott Meyers and Herb Sutter write against is use.

See this modest 12-line code above and you will note thart you malloc two types of things in a 5-line piece of code. Compare:

    My_data* cnt = (My_data*)malloc(sizeof(My_data));
    if (cnt == NULL) return NULL;  // error
    cnt->size = 0;                 // sure it is empty
    cnt->cap  = capacity;
    cnt->arr = (int*)malloc(capacity * sizeof(int));

with

    My_data* cnt = malloc(sizeof(My_data));
    if (cnt == NULL) return NULL;  // error
    cnt->size = 0;                 // sure it is empty
    cnt->cap  = capacity;
    cnt->arr = malloc(capacity * sizeof(int));

And may be you like the casts as a note to the reader of what is what. The argument of malloc is just an expression that evaluates to a number. Or just a number. So anything you can do to tell what is what is there to help. If you invert the expressions for example, the use of a cast can be a warning for you before your program crashes...

Anyway, lost are the days when we could choose the way we write code and not our school or our employer or client. There are coding conventions everywhere.

[on-topic]

Back to the code...

Note that we have a way to create My_data and now may be the time to think on how to destroy one. This is a good thing to do when writing C code to allocate things. BEFORE writing any more code in the program write and possibly test the other side:

See

My_data* so_destroy(My_data* gone)
{
    if (gone == NULL) return NULL; // safe
    // we have an array to free
    free(gone->arr); // free the array[]
    free(gone); // free the sruc itself
    return NULL;
}

A program like

int main(void)
{ 
    My_data* a_test = so_create(42);
    a_test          = so_destroy(a_test);
    return 0;
}

Can now be compiled and run. It is good for moral support. And to see that we can for sure free all data.

Why is this strange thing returning always NULL? Well, it is a warranty that a pointer will not the left in the code pointing to deleted memory, since it is se to NULL at the same time the struct it points to is destroyed.

is there any chance we will not want to display the data?

No, there is not. We are learning and we want reassurance of things.

So, write it in the first place... And we will add a title because i makes one miserable to keep writing printf()s to mark each test

int so_display(My_data* data, const char* title)
{
    if (title != NULL) printf("%s", title);
    if (data == NULL)
    {
        printf("\nArray does not exist\n");
        return -1;
    }
    printf(
        "\n[Array size is: %u of %u]:\n", data->size,
        data->cap);
    for (unsigned i = 0; i < data->size; i += 1)
        printf(
            "    %4d:  %s, %d\n", 1+i, data->arr[i].name,
            data->arr[i].age );
    printf(
        "[end: %d listed, for a capacity of %d "
        "records]\n\n",
        data->size, data->cap);
    return 0;
};

It is a simple thing. And it is handy to have an optional title. Now this program can run

int main(void)
{
    My_data* a_test = so_create(42);
    so_display(a_test, "still empty");
    a_test          = so_destroy(a_test);
    return 0;
}

and shows

still empty
[Array size is: 0 of 42]:
[end: 0 listed, for a capacity of 42 records]

Good, since we did almost nothing and already have some results. But we can probably create, destroy and display this thing we did not have in fact yet. You know, moral support.

what about some data?

Sure, let us write a function that insert data into the array... How to name such a thing? How to use such a thing?
Well, we want to insert a Detail record --- Pascal naming --- into our My_data thing:

        int so_insert( Detail* thing, My_data* block);

Why return an int? What about that great void?
We want to always return something, like 0 for success and some negative value for error, a C favorite.

Should it be complex? An implementation

int so_insert(Detail* thing, My_data* block)
{ 
    if (thing == NULL) return -1;
    if (block == NULL) return -2;
    // full, obvious
    if (block->size >= block->cap) return -3;
    block->arr[block->size] = *thing;
    block->size += 1;
    return 0;
};

Detail is only name and age so we can just copy. What can go wrong?

  • no valid Detail: return -1
  • no valid My_data: return -2
  • array is full: return -3

We already have so_display, and want to insert data but we will not go to something interactive. Interactive code is never a good plan.

What if we had a list of

  • "student 1", age "11"
  • "student 2", age "12"
  • "student 3", age "13"
  • "student 4", age "14"...

and so_insert() this Detail records until the array is full? Then we call so_display() to see if its ok and the so_destroy() to free the memory and for a beer we go...

Would it be complex? Well, not really...


Detail* so_factory()
{
  Detail* student = (Detail*) malloc(sizeof(Detail));
  if (student == NULL) return NULL;
  static number = 1000; // starting number
  student->age  = number;
  sprintf( student->name, "Student %6d", number); // char[15]
  number = number + 1;
  return student;
};

Just that: allocates a record to be inserted. The first one will have name= "Student 1", age=11, and the next will be 2 and it is fine

Again for moral support, this should work:

int main(void)
{
    My_data* a_test = so_create(42);
    so_display(a_test, "still empty");
    // get a record to insert
    Detail* one = so_factory();
    so_insert(one, a_test);
    free(one); //already copied
    so_display(a_test, "one record inserted");
    a_test = so_destroy(a_test);
    return 0;
}

And it does:

still empty
[Array size is: 0 of 42]:
[end: 0 listed, for a capacity of 42 records]

one record inserted
[Array size is: 1 of 42]:
       1:  Student   1000, 1000
[end: 1 listed, for a capacity of 42 records]

what about a full set?

Since we have so_factory and each struct has its own capacity and so_insert returns 0 for ok, it can be easy: just loop until error:

int main(void)
{
    My_data* a_test = so_create(4);
    int res = 0;
    while ( res == 0)
    {   // loop until error
        Detail* one = so_factory();
        res = so_insert(one, a_test);
        free(one); // already copied
    };
    so_display(a_test, "must be full now...");
    a_test = so_destroy(a_test);
    return 0;
}

and it shows

must be full now...
[Array size is: 4 of 4]:
       1:  Student   1000, 1000
       2:  Student   1001, 1001
       3:  Student   1002, 1002
       4:  Student   1003, 1003
[end: 4 listed, for a capacity of 4 records]

Not bad.

what about the user input?

Now we have the basics. If we can read data it is just a matter of calling so_insert and gone we are.

There are countless way to do this. Let us just read one by one in a line. This is not important. We say: Name: get a name, We say Age and get age. We build a Detail and so_insert() it as we already did... Well, we will just copy so_factory()and make some changes...

Is it complex? Let try

Detail* so_input()
{
    Detail* student = (Detail*)malloc(sizeof(Detail));
    if (student == NULL) return NULL;
    printf("    Name: ");
    char line[15];
    char* p = fgets(line, sizeof(line), stdin);
    if (p == NULL)
    {
        free(student);
        return NULL;  // fgets failed.
    }
    // remove a possible '\n' at the end
    int l = (int) strlen(line) - 1;
    if (line[l] == '\n') line[l] = 0;
    strcpy(student->name, line);
    printf("     Age: ");
    p = fgets(line, sizeof(line), stdin);
    if (p == NULL)
    {
        free(student);
        return NULL;  // fgets failed.
    }
    student->age = atoi(line);
    return student;
}

Just as planned. Note that fgets will add a \n at the end of name unless the user uses all space. Read the manual.

This code will do a test:

int main(void)
{
    My_data* a_test = so_create(4);
    int      res    = 0;
    Detail* one = so_input();
    if (one == NULL) return -1;
    so_insert(one, a_test);
    free(one);
    so_display(a_test, "one record was read...");
    a_test = so_destroy(a_test);
    return 0;
}

and we have

    Name: a b c d e
     Age: 42
one record was read...
[Array size is: 1 of 4]:
       1:  a b c d e, 42
[end: 1 listed, for a capacity of 4 records]

Sure, so_input was copied from so_factory.

I wrote this post right from the IDE, including the text itself, in order to show a way to go with this type of problem: use of a container in C, since almost all exercises are based in some sort of container.

complete code

It is not well tested. It is just one way of doing this, not the way of doing this. But worked at first test and shows a way to proceed. With code and arguments. Hope it helps.

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

typedef struct
{
    char name[15];
    int  age;
} Detail;

typedef struct
{
    unsigned cap;  // capacity
    unsigned size;
    Detail*  arr;  // and the data
} My_data;

My_data* so_create(unsigned capacity);
My_data* so_destroy(My_data* gone);
int      so_display(My_data* data, const char* title);
Detail*  so_input();
int      so_insert(Detail* thing, My_data* block);

Detail* so_factory();  // gets a numbered name/age pair

int main(void)
{
    My_data* a_test = so_create(4);
    int      res    = 0;
    Detail* one = so_input();
    if (one == NULL) return -1;
    so_insert(one, a_test);
    free(one);
    so_display(a_test, "one record was read...");
    a_test = so_destroy(a_test);
    return 0;
}

// https://stackoverflow.com/questions/76993795/how-to-dynamically-allocate-structure-memory-and-add-data-of-different-data-type

My_data* so_create(unsigned capacity)
{
    if (capacity == 0) return NULL;  // error
    My_data* cnt = (My_data*)malloc(sizeof(My_data));
    if (cnt == NULL) return NULL;  // error
    cnt->size = 0;                 // sure it is empty
    cnt->cap  = capacity;
    cnt->arr  = (Detail*)malloc(capacity * sizeof(Detail));
    if (cnt->arr == NULL)
    {  // malloc for the array failed
        free(cnt);
        return NULL;
    };
    return cnt;  // ok
}

My_data* so_destroy(My_data* gone)
{
    if (gone == NULL) return NULL;  // safe
    // we have an array to free
    free(gone->arr);  // free the array[]
    free(gone);       // free the sruc itself
    return NULL;
}

int so_display(My_data* data, const char* title)
{
    if (title != NULL) printf("%s", title);
    if (data == NULL)
    {
        printf("\nArray does not exist\n");
        return -1;
    }
    printf(
        "\n[Array size is: %u of %u]:\n", data->size,
        data->cap);
    for (unsigned i = 0; i < data->size; i += 1)
        printf(
            "    %4d:  %s, %d\n", 1 + i, data->arr[i].name,
            data->arr[i].age);
    printf(
        "[end: %d listed, for a capacity of %d "
        "records]\n\n",
        data->size, data->cap);
    return 0;
}

Detail* so_input()
{
    Detail* student = (Detail*)malloc(sizeof(Detail));
    if (student == NULL) return NULL;
    printf("    Name: ");
    char line[15];
    char* p = fgets(line, sizeof(line), stdin);
    if (p == NULL)
    {
        free(student);
        return NULL;  // fgets failed.
    }
    // remove a possible '\n' at the end
    int l = (int) strlen(line) - 1;
    if (line[l] == '\n') line[l] = 0;
    strcpy(student->name, line);
    printf("     Age: ");
    p = fgets(line, sizeof(line), stdin);
    if (p == NULL)
    {
        free(student);
        return NULL;  // fgets failed.
    }
    student->age = atoi(line);
    return student;
}

int so_insert(Detail* thing, My_data* block)
{
    if (thing == NULL) return -1;
    if (block == NULL) return -2;
    // full, obvious
    if (block->size >= block->cap) return -3;
    block->arr[block->size] = *thing;
    block->size += 1;
    return 0;
}

Detail* so_factory()
{
    Detail* student = (Detail*)malloc(sizeof(Detail));
    if (student == NULL) return NULL;
    static number = 1000;  // starting number
    student->age  = number;
    sprintf(
        student->name, "Student %6d", number);  // char[15]
    number = number + 1;
    return student;
};
arfneto
  • 1,227
  • 1
  • 6
  • 13
  • _"For the compiler it makes no difference. But for you, reading your code or, worst, reading code of others, this (cast the result) can be a life saver."_ this never happens, the cast is never recommended. You cite a books from the 95 and the cast is unncecessary since the inception of the `void *` which happened to be a fact of the start of the ANSI standard, just to allow generic pointers (against the previous `caddr_t` typedef from `char *`) This lack of `void *` was what enforced the use of the cast, and not the opposite, as you say. – Luis Colorado Aug 29 '23 at 08:51
  • @LuisColorado This is no what I said. I said the opposite: the book from '95 and the FAQ from 2000 sarted this _cult_. Implicit conversions are bad and even in that ancient text there is a note about this. And I showed that even in a 10-line piece of code it can make a diffeence. The rest is opinion and the company you work for. – arfneto Aug 29 '23 at 12:59
  • I don't know about the book you say. I'm programming in C since 1979 and I've lived the age of non-ansi-C programs with old style prototypes. `void` and `void *` comes to solve the problem with generic pointers, which it is something that `malloc()` does, and it's normal to find `int *var = malloc(something);` constructs in the code. Unnecessary casting is also a symptom of bad typing design, so why are you bolding that so much? I have never had an issue casting, but I have found many instances of the same recurring error (forgetting the `#include`) when not doing that. – Luis Colorado Aug 30 '23 at 05:27
  • ... I'd recommend never cast unnecessary (this is one case) and of course, only use `void *` for generic pointer and not elsewhere. That's a good hint! – Luis Colorado Aug 30 '23 at 05:31
  • @LuisColorado I do recommend always cast. As I explained, it is for the group not for the compiler. And in complex projects to always cast to a `typedef`ed thing. I will not say my own advice is _good_, as you said about yours. But you do not work for me and I do not work for you. Here it is just as opinion. People pays me for consulting. It is my job. They believe it is good for some reason. But not because I told them it is. – arfneto Aug 30 '23 at 12:53
  • @LuisColorado Why? As a reminder for the group of what the code is expecting to do with the pointer. In things we write, like network or editing software, is very common to `malloc` series of structs for things like `T**` or `T****` for example, in a few lines of code. And the cast brings a layer of assurance of what is what. This showed us many many bugs already. – arfneto Aug 30 '23 at 12:53
  • Casting is a symptom of bad design. If you cannot solve your programming issues but casting, then your design will probably have to be revised. Only for writing generic code in a language that does not support generic typing, is where solutions like `void *` are needed at all. I can accept seldom casting, but as a convenience, never!!! I will allways recommend to make a comment in every cast you make to justify why it is needed, for the same reason explained here... if you need to convert a type to another to make an assignment, some of them (the source or the target) should be redefined. – Luis Colorado Aug 31 '23 at 08:46
  • There's no need to remember how `malloc()` works and what the variable is, as normally the idiom is to write `type *variable = malloc(sizeof *variable);`, the type is well indicated in the local variable declaration, then you can return the pointer type of `variable`. No need to cast. – Luis Colorado Aug 31 '23 at 08:49
  • If you have `My_data* cnt = (My_data*)malloc(sizeof(My_data));`, and you later decide to change the type `My_data` for something else, you will have to change three places in the same line instead of one, if you write instead `My_data *cnt = malloc(sizeof *cnt);`. – Luis Colorado Aug 31 '23 at 08:52
  • The only case I admit that justifies the use of casting in `malloc` is if you, for example, are using a C++ framework (like GTest) to do unit testing C code, in which is required to compile your C code as C++ (which doesn't allow to automatically convert `void *` to another pointer type) In only that case, I admit that the cast is necessary. In C++ code the use of `malloc()` is not recommended, but the case above is an exception, so the cast is needed. – Luis Colorado Aug 31 '23 at 08:55
  • I do not know your common use case for `C` but `type *variable = malloc(sizeof *variable);` is _normal_ in textbooks. In many fields the _normal_ is `malloc(expression)` and that expression evaluates to a `size_t`, so the type is not indicated at all the way you said. In many cases the expression is a function call that look at the wire and tell you by the headers what type of data is coming in and what `type` of thing is supposed to be allocated. – arfneto Aug 31 '23 at 14:21
  • _you will have to change three places in the same line_ as you said, is precisely the reason I told you these things can be saviors: if you need to change something in many places your team will have less and less chance of doing that by mistake. And these kind of bugs are very very costly to hunt for and correct: namely allocating a buffer of the wrong size and that can work sometimes and crash sometimes. – arfneto Aug 31 '23 at 14:22
  • You can call it `bad design`. You can even tell this to the designers of languages that requires a type, like prof. Stroustrup... – arfneto Aug 31 '23 at 14:23
0

There are a few errors in your code, as well as some other issues that, ideally, should be addressed.

First and foremost is your memory allocation line:

details* ptr = (details*)malloc(sizeof(details));

This allocates sufficient memory for one and only one structure. You need to use the given value of n (the number of nodes you want) as a size multiplier:

details* ptr = malloc(n * sizeof(details));

The other 'error' in your code is using the & (address-of) operator on your name member, in the call to scanf in the entry function:

scanf("%s%*c", &(det + i)->name); // Wrong

For character arrays using the %s format specifier, the array name itself should be the argument, as this is automatically converted ('decays') to the address of the array's first element (which is what the %s specifier expects). So, this is the 'correct' version:

scanf("%s%*c", (det + i)->name); // Right (no &)

However, as you have noted, that will not allow input of strings that contain spaces and, if such is given (like "Tom Jones"), the characters after the (first) space will be left in the input buffer and then the following call to scanf (attempting to read the age) will fail.

As mentioned in the comments, for reading strings with spaces in them, the fgets function is far easier to use:

fgets(det->name, 15, stdin); // Will read AT MOST 14 chars plus a null terminator

There are a few other 'minor' issues in your code, like using the size_t type is better for counts and sizes, and that you never free the memory you allocate. Here is a version of your code with the issues I've mentioned (and some others) addressed. The comments should help you understand what I've changed and why:

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

typedef struct node {
    char name[15];
    int age;
} details;

void entry(details*, size_t);

int main()
{
    size_t n, i; // Better to use the size_t type for counts and sizes
    printf("Enter number of details\n");
    if (scanf("%zu%*c", &n) != 1) { // Use the %zu specifier for size_t
        perror("Invalid input!\n"); // Always check the return value and handle errors!
        return 1;
    }
    details* ptr = malloc(n * sizeof(details)); // YOU MUST MULTIPLY BY "n"!!
    entry(ptr, n);
    for (i = 0; i < n; i++) {
        printf("\n%s\n%d\n\n", ptr[i].name, ptr[i].age); // Use [] - its easier/clearer
    }
    free(ptr); // Don't forget to free the memory
    return 0;
}

void entry(details* det, size_t n)
{
    for (size_t i = 0; i < n; i++, det++) { // Increment the pointer on each loop ...
        printf("Enter name:");              // ... it's only a local copy.
        fgets(det->name, 15, stdin);        // Use fgets to read spaces in the string
        printf("Enter Age:");
        if (scanf("%d%*c", &det->age) != 1) {
            perror("Invalid input!\n");
            exit(1);
        }
    }
}

On the issue (also raised in the comments) of casting the malloc return value in C, see: Do I cast the result of malloc?.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83