0

I'm doing some tests with functions that return arrays and, with some reading, I came with this:

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

int i;

char * askData() {
    static char data[5];
    for(i = 0; i < 5; i++)
        scanf("%s", &data[i]);

    return data;
}

int main() {
    char* data = askData();
    
    printf("Flag 1\n");
    for(i = 0; i < 5; i++)
        printf("Loop %i: %s\n", i, &data[i]);

    printf("Flag 2\n");
}

What I want to do is to print each element from the pointer separately (much like an array, but I didn't used it because aparently a C function can't return an array).

Input:

Harry
Hermione
Ronald
Draco
Voldemort

Intended Output:

Flag 1
Loop 0: Harry
Loop 1: Hermione
Loop 2: Ronald
Loop 3: Draco
Loop 4: Voldemort
Flag 2

Current Output:

Flag 1
Loop 0: HHRDVoldemort
Loop 1: HRDVoldemort
Loop 2: RDVoldemort
Loop 3: DVoldemort
Loop 4: Voldemort
Flag 2
rookie
  • 126
  • 10
  • 2
    Look closely at what the format options mean and what & means. – john elemans Feb 14 '22 at 16:43
  • looks like you intend to use `data` for single `char`s rather than a string, change your input to `scanf(" %c", &data[i]);`. The space before `%c` [is not a typo](https://stackoverflow.com/questions/5240789/scanf-leaves-the-newline-character-in-the-buffer) – yano Feb 14 '22 at 16:56
  • @johnelemans I'm kind confused with it, I already tried *(data + i) and data[i], but those give me errors. And I was not explicit in my example, but askData is intended to ask for names, store in an array and display them later, so that's why (I guess) it needs to be %s – rookie Feb 14 '22 at 16:56
  • @yano yeah I didn't used a good example, I intend to use data for strings. I will edit the question – rookie Feb 14 '22 at 16:58
  • 1
    What does data[5] allocate in memory? Try an example with an array of integers to compare. – john elemans Feb 14 '22 at 16:59
  • 1
    In that case it makes no sense to loop through each character of `data` asking for input. A single string is 1 or more `char`s terminated with a NUL terminator `'\0'`. `data` is only capable of holding one string. Do `scanf("%s", data);` without the loop. Note that the string you input can only be 4 chars to leave room for the NUL terminator, otherwise you'll overflow `data` causing [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior). – yano Feb 14 '22 at 16:59
  • 1
    A function cannot return an array, as you apparently know, so it isn't quite clear what you are testing and why. What problem are you trying to solve? Returning a pointer to a static array is more likely to be a problem than a solution. – n. m. could be an AI Feb 14 '22 at 17:03
  • @johnelemans ok, now I see what you are trying to say, I tried with ints and saw what is wrong: https://pastebin.com/CYhbzgiK – rookie Feb 14 '22 at 17:13
  • But @yano pointed out something interesting either, I don't know why I wrote like that (probably using python for too long), but data is supposed to be an array with five informations: data[] = [name, phone, id, stuff, stuff]. So I should alocate more space to data so it can fit all the information? – rookie Feb 14 '22 at 17:17
  • @n.1.8e9-where's-my-sharem. you have a fair point, what I want to do with this kind of function is to clear a project of mine, which ask for the same data in a couple of options inputted by the user. So, to clean this code, I want to create a single function that does that. – rookie Feb 14 '22 at 17:19
  • 1
    If you want data to hold 5 fields, then you should create a struct and an array of that struct, something like `struct data{ char name[64]; char phone[32]; int id; // etc };` Then do `struct data myData[10];` for 10 records. But that's not indicated anywhere in the question, which has now become a moving target. – yano Feb 14 '22 at 17:33
  • 1
    @yano Yes, sorry for being vague, my english is kinda rusty and very limited to express myself. Those were the only details that were left out. I will take some time to digest everything that was said here and try to implement it. – rookie Feb 14 '22 at 17:38

3 Answers3

1

in printf("Loop %i: %s\n", i, %data[i]);

you are printing a char that's why %c is required and while printing % is not required

printf("Loop %i: %c\n", i, data[i]);
 #include <stdio.h>
   #include <stdlib.h>
   
   char * askData() {
       static char data[5];
       for(i = 0; i < 5; i++)
           scanf("%s", &data[i]);
   
       return data;
   }
   
   int main() {
       char* data = askData();
       int i; //its better here

       printf("Flag 1\n");
       for(i = 0; i < 5; i++)
           printf("Loop %i: %c\n", i, data[i]);
   
       printf("Flag 2\n");
   }
narayann
  • 361
  • 3
  • 6
1

data is an array of chars, only capable of holding a single string that is 4 chars long. It is incorrect to loop through each char of data trying to store a string there. You should get rid of the loop in askData, and instead call that function in a loop, similar to below.

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

#define NUM_NAMES 5

// no point in making i global

char * askData() {
    // 5 chars can only hold a string of 4, not big enough for the names
    // you indicated. Memory is cheap, use some
    static char data[256];
    // limit scanf to read one less than the size of data, so as not to overflow
    scanf("%255s", data);

    return data;
}

int main(void) {
    printf("Flag 1\n");
    // better practice to use size_t types when working with sizes
    for(size_t i = 0; i < NUM_NAMES; i++){
        // call askData in a loop. Each call will overwrite the data there from the previous call
        char* data = askData();
        // %zu is the correct format specifier for printing size_t type
        printf("Loop %zu: %s\n", i, data);
    }

    printf("Flag 2\n");
}

Note, if you want to save each input rather than overwriting, you'll need a 2D array, something like data[NUM_NAMES][256];, then you could call askData once and reinstate the loop in that function, or continue to call askData in a loop and pass in the string index as an argument.

Demonstration

yano
  • 4,827
  • 2
  • 23
  • 35
1

So you're right C doesn't return arrays in the sense that you are talking about, however the way we do that in C is by utilizing two ideas together:

  1. Working with memory: By allocating a region of memory and then returning the starting location of that memory region.

    The function call to allocate memory is called malloc and then you have to use the free call to release that memory region.

  2. Pointer Arithmetic: I'm sure you already know that C has some basic types (char, int, long, float, double, and lately bool); each takes a different amount of memory and so C also provides the convenience of understanding typed pointers (pointers are variables that mark a memory location). A typed pointer understands how "big" it's associated type is in bytes and when you increment its value by 1 in C internally it's incremented by the size of the type.

Combining these ideas we can get arrays:

So for example if you have an array of five integers then you would allocate a region that is 5 * sizeof(int) ; Assuming a modern system the sizeof(int) is going to be 32-bits (or 4 bytes); so you will get a memory region of 20 bytes back and you can fill it up with the values.


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

int * get_numbers(size_t count) {

     int *data = malloc(count * sizeof(int));
     if (!data) {
         return NULL; // could not allocate memory
     }

     // notice that we are increasing the index i by 1
     // data[i] shifts properly in memory by 4 bytes
     // to store the value of the next input.

     for(int i=0; i < count; i++) {
           scanf("%d", &data[i]);
     }
}

int main() {
     int *data = get_numbers(5);
     if (data == NULL) {
          fprintf(stderr, "Could not allocate memory\n");
          exit(-1);
     }

     for(int i = 0; i < 5; i++) {
         printf("data[%d] = %d\n", i, data[i]);
     }

     free(data); 
}

Now in your case you have a bit of a complication (but not really) you want to be able to read in names, which are "strings" which don't really exist in C except as array of characters.

So to rephrase our problem is that want an array of names. Each name in itself is an array of characters. The thing is, C also understands type pointer pointers (yes a pointer to pointers) and can figure out the pointer arithmetic for that.

So we can have a memory region full of different types; the type pointers themselves can have a list of pointers in a memory region; and then make sure that each pointer in that list is pointing to a valid memory region itself.


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


char *get_one_name() {
    char *name = NULL;
    size_t len = 0;
    printf("Enter a name:");
    // getline takes a pointer, which if it's set to NULL
    // will perform a malloc internally that's big enough to
    // hold the input value.
    getline(&name, &len, stdin);
    return name;
}

char ** get_names(size_t count) {

    // this time we need memory region enough to hold
    // pointers to names
     char **data = malloc(count * sizeof(char *));
     if (!data) {
         return NULL; // could not allocate memory
     }

     for(int i=0; i < count; i++) {
         data[i] = get_one_name();
     }

     return data;
}

void free_names(char **data, size_t count) {
    for(int i = 0; i < count) {
        if(data[i] != NULL) {
            free(data[i]);
        }
    }
    free(data);
}

int main() {
     char **data = get_names(5);
     if (data == NULL) {
          fprintf(stderr, "Could not allocate memory\n");
          exit(-1);
     }

     for(int i = 0; i < 5; i++) {
         printf("data[%d] = %d\n", i, data[i]);
     }

     free_names(data, 5);
}

Hopefully this gives some ideas to you.

Ahmed Masud
  • 21,655
  • 3
  • 33
  • 58