3

How to print array of string by taking of user input? I am confused between str+i and str[i].

In my program, strings are not printed. It takes 5 strings as input, after that program terminates

#include <stdio.h>
int main()
{

    char *str[5];

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

    for (int i = 0; i < 5; i++)
    {

        printf("%s\n", str[i]);
    }
}

So firstly, go and read the answers, then come here for complete code. By span of 2 days, I arrived at two solutions to my problem with the help of below answers.

//1.
#include <stdio.h>
#include<stdlib.h>
int main()
{

    char* str[5];

    for (int i = 0; i < 5; i++)
    {
        str[i] = (char *)malloc(40 * sizeof(char));
        scanf("%s", str[i]);
    }

    for (int i = 0; i < 5; i++)
    {

        printf("%s\n", str[i]);
    }

   
}

//2

#include <stdio.h>
#include<stdlib.h>
int main()
{

    char str[5][40];

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

    for (int i = 0; i < 5; i++)
    {

        printf("%s\n", str[i]);
    }

   
}
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • 1
    If you are looking at some really malicious code, you might even see something like `i[str]`... – DevSolar Jul 13 '21 at 14:10
  • 2
    @DevSolar I mention that in my answer, but I should point out that the OP is Lucifer The devil, so one should be extra careful in mentioning things that can damn your soul for all eternity. – Cort Ammon Jul 13 '21 at 14:24

2 Answers2

10

Strings are a painful learning experience in c. Its quite unlike higher level languages.

First off, to answer your explicit question, str[i] is the value of the i-th element in an array. If str points to the array of characters"Hello", then str[1] is the value "e". str + i, on the other hand, is a pointer to the i-th element in an array. In the same situation, where str points to the array of characters "Hello", str + 1 is a pointer to the "e" in that array. str[i] and *(str + i) are identical in every way. In fact, the spec defines a[b] to be (*(a + b)), with all of the behaviors that come with it! (As an aside, C still supports a very ancient notation i[str] which is exactly the same as str[i] because addition of pointers and integers is commutative. You should never use the backwards form, but its worth noting that when I say they are defined to be the same, I mean it!)

Note that I have been very careful to avoid the word "string," and focused on "character arrays" instead. C doesn't technically have a string type. This is important here because you can't do easy things like std::string str[5] (which is a valid C++ notation to create an array of 5 strings) to get a variable length string. You have to make sure you have memory for it. char *str[5] creates an array of 5 char*, but doesn't create any arrays of characters to write to. This is why your code is failing. What is actually happening is each element of str is a pointer to an unspecified address (garbage memory from whatever was there before the variable was created), and scanf is trying to assign into that (nonexistent) array. Bad things happen when you write to somewhere random in memory!

There are two solutions to this. One is to use Serve Lauijssen's approach using malloc. Just please please please please please remember to use free() to deallocate that memory. In nearly any real program, you will not want to leak memory, and using free is a very important habit to get into early on. You should also make sure malloc did not return null. That's another one of those habits. It virtually never returns null on a desktop, but it can. On embedded platforms, it can easily happen. Just check it! (And, from the fact that I have to be reminded of this in the comments suggests I failed to get in the habit early!)

The other approach is to create a multidimensional array of characters. You can use the syntax char str[5][80] to create a 5x80 array of characters.

The exact behavior is a bit of a doozie, but you will find it just happens to behave the way you think it should in your case. You can just use the syntax above, and keep moving. However, you should eventually circle back to understand how this works and what is going on underneath.

C handles the access to these multidimensional arrays in a left to right manner, and it "flattens" the array. In the case of char str[5][80], this will create an array of 400 characters in memory. str[0] will be a char [80] (an 80 character array) which is the first 80 characters in that swath of memory. str[1] will be the next swath of 80 characters, and so on. C will decay an array into a pointer implicitly, so when scanf expects a char*, it will automatically convert the char [80] that is the value of str[i] into a char* that points at the first character of the array. phew

Now, all that explicit "here's what's actually going on" stuff aside, you'll find this does what you want. char str[5][80] will allocate 400 characters of memory, laid out in 5 groups of 80. str[i] will (almost) always turn into a char* pointing at the start of the i-th group of characters. Then scanf has a valid pointer to an array of characters to fill in. Because C "strings" are null-terminated, meaning they end at the first null (character 0 aka '\0') rather than at the end of the memory allocated for it, the extra unused space in the character array simply wont matter.

Again, sorry its so long. This is a source of confusion for basically every C programmer that ever graced the surface of this earth. I am yet to meet a C programmer who was not initially confused by pointers, much less how C handles arrays.

Three other details:

  • I recommend changing the name from str to strs. It doesn't affect how the code runs, but if you are treating an object as an array, it tends to be more readable if you use plurals. If I was reading code, strs[i] looks like the i-th string in strs, while str[i] looks like the i-th character in a string.
  • As Bodo points out in comments, using things like scanf("%79s", str[i]) to make sure you don't read too many characters is highly highly highly desirable. Later on, you will be plagued by memory corruptions if you don't ingrain this habit early. The vast majority of exploits you read about in major systems are "buffer overruns" which are where an attacker gets to write too many characters into a buffer, and does something malicious with the extra data as it spills over into whatever happens to be next in the memory space. I'm quite sure you aren't worried about an attacker using your code maliciously at this point in your C career, but it will be a big deal later.
  • Eventually you will write code where you really do need a char**, that is a pointer to a pointer to a character. The multidimensional array approach won't actually work on that day. When I come across this, I have to create two arrays. The first is char buffer[400] which is my "backing" buffer that holds the characters, and the second is char* strs[5], which holds my strings. Then I have to do strs[0] = buffer + (0 * 80); strs[1] = buffer + (1 * 80); and so on. You don't need this here, but I have needed it in more advanced code.
    • If you do this, you can also follow the suggestion in the comments to make a static char backing[400]. This creates a block of 400 bytes at compile time which can be used by the function. In general I'd recommend avoiding this, but I include it for completeness. There are some embedded software situations where you will want to use this due to platform limitations. However, this is terribly broken in multithreading situations, which is why many of the standard C functions that relied on static allocated memory now have a variant ending in _r which is re-entrant and threadsafe.
    • There is also alloca.
Cort Ammon
  • 10,221
  • 31
  • 45
  • A 'third' approach would be static heap allocation. – Zilog80 Jul 13 '21 at 13:58
  • @Zilog80 You are referring to `alloca`? – Cort Ammon Jul 13 '21 at 13:59
  • "C doesn't technically have a string type" --> OP did not assert it was a type. C has _string_ as "A string is a contiguous sequence of characters terminated by and including the first null character.". Less need to describe what C does not have or what C++ has and more useful to describe what C has/does. – chux - Reinstate Monica Jul 13 '21 at 13:59
  • 1
    @chux-ReinstateMonica True. However, the particular error we see in the OP's code is a consequence of *thinking* of strings as types, even if the words were not used. – Cort Ammon Jul 13 '21 at 14:00
  • No, i'm referring to `static char buffer[400];` as a way to allocate space statically at load time. – Zilog80 Jul 13 '21 at 14:02
  • 1
    @Zilog80 Ahh. I'll add that, although I'm going to recommend against it in the general case. – Cort Ammon Jul 13 '21 at 14:03
  • For malloc/free, it could useful to specify that each malloc/calloc/realloc return should always be checked as it can fail. – Zilog80 Jul 13 '21 at 14:13
  • Perhaps I am self-centered in my recommendations, but I've been burned too many times by a library that thought they'd avoid a few extra allocations by using statics, and as a result, forced me to wrap every call into the library to avoid race cases. Then there was the time where both my library and the final user application used the same sub-library, such that I couldn't wrap all of the calls... needless to say I prefer my statics actually be static ;-) – Cort Ammon Jul 13 '21 at 14:14
  • @Zilog80 Added. Man I've grown soft in my C++ programming days! All these new fangled things like throwing exceptions have made my job too easy! – Cort Ammon Jul 13 '21 at 14:19
  • @Cort Ammon, in my code in printf statement if i put (str+i) instead of (str[i]) then it runs perfectly so im asking isnt (str+i) is the address of pointer whois pointing to an char array so if yes then why it runs properly in that case – Lucifer The devil Jul 13 '21 at 15:48
  • @LuciferThedevil Oh I am so sorry. That is even worse. The short answer is it runs "properly" because you get lucky. `scanf` is a varadic function call, and in C those forget about types entirely. All scanf knows is it got a pointer, and the `%s` lets it know that was supposed to be a `char*`. In the case of `&(str + i)`, the type ends up actually being a `char**`, and you just end up writing all over the bytes of that memory space. I do believe this is a case where two wrongs make a right. Because your `scanf` code and `printf` code are similar, they probably made the same mistake. – Cort Ammon Jul 14 '21 at 17:51
  • The reality is that, because there's no memory being allocated for the strings, every behavior is going to be wrong. For your own curiosity, we can talk about what any one of these wrong behaviors might be, and why C does it that way. But even these simple examples rest terribly close to some very specific and detailed aspects of how C works. If you get it right, you don't have to worry about any of these details. But if you want to know what happens when you get it wrong in a particular way... well... as you can see from my comments, it's a rabbit hole. – Cort Ammon Jul 14 '21 at 17:54
1

Since you have defined char *str[5], so str should be of type char **. So when scanf() expects char *, it is given &str[i] which is of type char **, and so it is not right. That is why your printf("%s\n", str[i]) might not work.

The (str+i) (which is of type char ** and not the same as str[i]) method might work with printf() in the above case because you are reading string values to &str[i]. This could be fine when you have input strings which are short. But then, reading values to &str[i] is not what you intend here, because str[i] is already of type char *. Try giving a very very long string as input (like aaaaaaa...) in the above code, and it will probably give you a Segmentation fault. So technically the method is broken, and you really need to allocate memory to each char * elements in your str array before reading in strings. For example, you can do

for (int i=0; i<5; ++i)
  str[i] = (char *) malloc(length_you_wish);

Since each str[i] is of type char * and scanf() expects argument of type char *, use scanf( "%s", str[i] ), omitting & before str[i].

Now, you can printf() with printf( "%s\n", str[i] ) or printf( "%s\n", *(str+i) ), where str[i] or *(str+i) is of type char *.

Lastly, you need to do

for (int i=0; i<5; ++i)
  free(str[i])

Moral: str[i] is a pointer to a specific char array (it holds the address of the first char element of the array), but (str+i) points to str[i] (it refers to the address of str[i], so (str+i) and &str[i] should have same value).

XCSM
  • 99
  • 6