Varun Behl
Varun Behl

Reputation: 43

How to scan a user defined number of strings in C?

If the user enters 5 as the first input, then the program should proceed with scanning 5 strings.

I tried:

int n;
scanf("%d",&n);
char a[100][10];

for (int i = 0; i < n; i++) {
    scanf("%s", &a[i][10]);
}

but that only scans a single word and then exits.

Upvotes: 0

Views: 4985

Answers (4)

corazza
corazza

Reputation: 32344

&a[i][10] evaluates to the address of the 11th element of the ith array in a - however since your arrays only have ten elements this will eventually go out of bounds.

You need the address of the i-th array. You can get that with a[i].

Upvotes: 1

Elias Van Ootegem
Elias Van Ootegem

Reputation: 76395

Given the fact that most of the answers given here are flawed in some way or another:
The accepted answer, for instance, states that a[i][10] accesses the tenth char in the row. This is completely false. As arrays are zero-indexed, this actually attempts to access the eleventh char in a row, which could well be out of bounds.
The same answer shows how a 2D array is divided up in rows and columns. This, too, is false. An array, be it 2D or 1D is a contiguous block in memory. Just one big row of chars. The actual layout of your array a[100][10], then looks like this. I've just noticed the answer mentions this, but still:

|/a[0][0] ===============================\\===============>a[99][9]\|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|

Just 100*10 chars in a row. Accessing a[1][0], then is the same as accessing a[0][10] or indeed a[10]. The out-of-bounds risk of using a[i][10] occurs if i is 99, because as you can see, the last char in the array is situated at a[99][9]. All memory beyond that point is off-limits.
The example given, in rows and column format where the input is a, b and c, then does not look like this:

_ _ _ _ _ _ _ _ _ a 
_ _ _ _ _ _ _ _ _ b
_ _ _ _ _ _ _ _ _ c

But rather, it'll look like this:

0 1 2 3 4 5 6 7 8 9 
_ _ _ _ _ _ _ _ _ _
a _ _ _ _ _ _ _ _ _ 
b _ _ _ _ _ _ _ _ _
c _ _ _ _ _ _ _ _ _

@TomFenech's answer is a lot better, using fgets here makes sense. However, to dot the i's and cross the T's: the nul-terminating char won't be read by fgets. The function will read at most n-1 chars, where n is the specified max length, as the documentation states:

The fgets function reads at most one less than the number of characters specified by n from the stream pointed to by stream into the array pointed to by s. No additional characters are read after a new-line character (which is retained) or after end-of-file.

However, fgets stops reading if, and only if it encounters a line-break or an EOF. It will not stop after encountering a NUL char. Keep that in mind, but in your case, it shouldn't be a worry.

When using fgets, however, you can find tons of snippets that call fflush(stdin); up front, to clean the input buffer. This invokes undefined behaviour, however, and you do well reading the buffer until it's empty first. Tom Fench left this out, so for completeness, I thought I'd mention it here, too. Other than that, I'd say his answer is accurate.

So, with this in mind, here's my suggestion:

Do away with your fixed-sized buffer. How many strings a will contain depends on user input. That means the array itself should be one of variable length. Thankfully, C (since C99) has just the thing you need: Variable Length Arrays - or VLA's for short would be preferable here. Here's what I wrote as a quick test/proof of concept:

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

int main ( void )
{
    int i, n;
    puts("How many words do you wish to enter?");
    scanf(" %d", &n);//get size of input array
    while((i=getchar()) != EOF && i != '\n');//"flush" stdin
    char a[n][10];
    printf("Please enter %d words now\n", n);
    for (i=0;i<n;++i)
        fgets(a[i], 9, stdin);//get 9 char input, not 10 (10th char is nul terminator)
    for (i=0;i<n;++i)
        printf("Line %d: %s\n", i+1, a[i]);//print each input line
    return 0;//return
}

You can compile this on a *NIX system using gcc:

$ gcc -std=c99 your_code.c -o progName
$ ./progName

And run it. It works as expected.
Mind you, the input buffer for each entry is only 10 chars long. That means that a word like: "successful" is a no-go (it's 10 chars long, but a string requires a NUL-terminating char, too). I'd choose to use a bigger buffer for the actual input if I were you.
Since we're using a VLA, we no longer use up 1000 bytes of stack space by default (a[100][10] uses that much). If the value of n is 10, we could make our buffer 100 chars big, and still end up using no more memory than you are doing now. So perhaps consider using:

char a[n][24];
for (i=0;i<n;++i)
    fgets(a[i], 24, stdin);

Ideally, you'd clean the stdin buffer after each fgets call in the loop, too:

int j;//for clearing the buffer
for (i=0;i<n;++i)
{
    fgets(a[i], 24, stdin);
    if (strlen(a[i]) >= 23)//buffer overflow averted, clear stdin
        while ((j = getchar()) != EOF && j != '\n');//clear buffer
}

If you don't, and you leave the buffer at 10, you could encounter something like this:

Please enter 2 words now
successful?
Line 1: successfu
Line 2: l?

The full code, now looks something like this:

#include <stdio.h>
#include <stdlib.h>
#include <string.h> //add this

int main ( void )
{
    int i, j, n;//extra temp var
    puts("How many words do you wish to enter?");
    scanf(" %d", &n);
    while((j=getchar()) != EOF && j != '\n');
    char a[n][10];
    printf("Please enter %d words now\n", n);
    for (i=0;i<n;++i)
    {
        fgets(a[i], 10, stdin);
        if (strlen(a[i] >= 9)//<-- added
            while ((j=getchar()) != EOF && j != '\n');//added
    }
    for (i=0;i<n;++i)
        printf("Line %d: %s\n", i+1, a[i]);
    return 0;//return
}

The program compiles and runs in exactly the same way, though.
If you want to accommodate larger buffers if the user only wants to pass, say, 2 words as input, you can use VLA's for that, too:

#include <stdio.h>
#include <stdlib.h>
#include <string.h> //add this

//max buffer total 1000 chars is what you are using with a[100][10]
#define MAX_TOTAL_BUFFER 1000
//don't allow more than 80 chars per entry
#define MAX_SINGLE_BUFFER 80
//added for safety
#define MIN_SINGLE_BUFFER 10

int main ( void )
{
    int i, j, n. buf_len;
    puts("How many words do you wish to enter?");
    scanf(" %d", &n);
    buf_len = MAX_TOTAL_BUFFER/n; // int/int yields int 1000/3 == 333
    if (buf_len > MAX_SINGLE_BUFFER)
        buf_len = MAX_SINGLE_BUFFER;
    else if (buf_len < MIN_SINGLE_BUFFER) //mind you, risk of stack overflow here
        buf_len = MIN_SINGLE_BUFFER;
    while((j=getchar()) != EOF && j != '\n');
    char a[n][buf_len];
    printf("Please enter %d words (max %d chars long)\n", n, buf_len-1);
    for (i=0;i<n;++i)
    {
        fgets(a[i], buf_len, stdin);
        if (strlen(a[i] >= buf_len)//<-- added
            while ((j=getchar()) != EOF && j != '\n');//added
    }
    for (i=0;i<n;++i)
        printf("Line %d: %s\n", i+1, a[i]);
    return 0;//return
}

Play around with the max and minima for the buffers, and see how far you can push this until your stack eventually overflows. Also check the value of n after you get it from the user. If the user passes 0, this program doesn't deal with it properly. Either prompt the user again for a positive n, use a default value, or exit.
If n is negative, either use its absolute value (or multiply by -1), prompt again or exit... all of these things make for good exercises in interactive CLI C programs. Separating the logic, and writing functions will prove useful to turn this snippet into actual, useful code.

Upvotes: 2

Tom Fenech
Tom Fenech

Reputation: 74596

scanf is dangerous in this situation, as a string longer than the length of your array could be entered. I would recommend using fgets:

#include <stdio.h>

int main()
{
  int n;
  scanf("%d ",&n); // note the added space
  char a[100][10];
  for (int i = 0; i < n; i++) {
      fgets(a[i], 10, stdin);
  }
  return 0;
}

The middle argument to fgets specifies the maximum number of characters to read. fgets will read max - 1 characters and adds a \0 (null byte) to the end of the string. Using this approach protects you from a buffer overflow and ensures that you end up with a valid string.

The added space in the scanf is so that the newline after the number is swallowed as well.

Upvotes: 1

APan
APan

Reputation: 366

char a[100][10];
_ _ _ _ _ _ _ _ _ _ 
_ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _
         |
         |
    100 rOWS and 10 columns
         |
         |
_ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _

When you do scanf("%s", &a[i][10]);, you are taking input from stdin for just the tenth character of every row.

For example if n=3 and inputs are a, b, c then it will logically look something like this(memory is continous but just for the sake of understanding I am using rows and columns).

_ _ _ _ _ _ _ _ _ a 
_ _ _ _ _ _ _ _ _ b
_ _ _ _ _ _ _ _ _ c

So to input a whole string you should just give the base address of row.

scanf("%s", &a[i]);

Upvotes: 0

Related Questions