On the C book, (second edition page 29), I read the following content:
/* getline: read a line into s, return length */
int getline(char s[], int lim)
{
int c, i;
for(i=0; i<lim-1 && (c=getchar())!=EOF && c!='\n'; ++i)
s[i] = c;
if(c == '\n') {
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
}
My question is: why it is i<lim-1 but not i<lim in the for condition test? (ps: lim is the maximum length of a line)
Question 2: On C, is EOF counted as a character?
Space needs to be reserved for the null-terminator \0 that is appended to the string at the end of the loop. (This is how strings are modelled in C).
EOF is a special value that denotes the end of a file. Note how getchar() returns an int: this is chiefly so the value of EOF doesn't have to be within the range of a char.
Related
The following is getline implementation in K&R on page 29 ,
# define MAXLINE 1000
int getLine(char s[], int lim)
{
int c ,i ;
for(i = 0; i < lim - 1 && (c = getchar()) != EOF && c!= '\n'; ++i)
s[i] = c;
if(c == '\n'){
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
I don't understand why we need to do "i < lim - 1" here in the for loop. For correct indexing, why doesn't " i < lim " suffice ?
Any help would be much appreciated...
The function is going to build a string (stored in the array specified by the parameter char s[]) that is a sequence of characters terminated with the zero terminating character '\0'.
So one element of the array is reserved for the terminating zero character that is appended to the character array before exiting the function
s[i] = '\0';
For example if the variable lim is equal to 5 then you may enter in the array no more than 4 characters in the range of indices [0, 3]. In this case (if all lim-1 characters will be filled) in the last element of the array there will be written the terminating zero character '\0' at position 4.
i could not understand what mgetline does in this code.
anyone can help me?
int mgetline(char s[],int lim)
{
int c, i;
for(i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = c;
if(c == '\n')
{
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
The function basically reads characters one-by-one from the the standard input stream stdin until you enter a \n (newline) or the array limit of s, lim, is reached. The characters are stored in the char s[] and the length of what was read is returned.
It's hard to answer with more detail since it's a little unclear what it is you don't understand, but I've tried to annotate the code to make it somewhat clearer.
This is the same code, only reformatted to fit my comments.
int mgetline(char s[], int lim) {
int c, i;
for(i = 0; // init-statement, start with `i` at zero
i < lim - 1 // condition, `i` must be less than `lim - 1`
&& // condition, logical AND
(c = getchar()) !=EOF // (function call, assignment) condition, `c` must not be EOF
&& // condition, logical AND
c != '\n'; // condition, `c` must not be `\n` (newline)
++i) // iteration_expression, increase i by one
{
s[i] = c; // store the value of `c` in `s[i]`
}
if(c == '\n') { // if a newline was the last character read
s[i] = c; // store it
++i; // and increase i by one
}
s[i] = '\0'; // store a null terminator last
return i; // return the length of the string stored in `s`
}
In the condition part of the for loop you have 3 conditions that must all be true for the loop to enter the statement for(...;...;...) statement. I've made that statement into a code block to make it easier to see the scope. EOF is a special value that is returned by getchar() if the input stream (stdin) is closed.
Note: If you pass an array of one char (lim == 1) this function will cause undefined behavior. Any program reading uninitialized variables has undefined behavior - and that's a bad thing. In this case, if lim == 1, you will read c after the loop and c will then still be uninitialized.
Either initialize it:
int mgetline(char s[], int lim) {
int c = 0, i;
or bail out of the function:
int mgetline(char s[], int lim) {
if(lim < 2) {
if(lim == 1) s[0] = '\0';
return 0;
}
int c, i;
My question is concerning some code from section 1.9-character arrays in the Kernighan and Ritchie book. The code is as follows:
#include <stdio.h>
#define MAXLINE 1000 /* maximum input line size */
int getline(char line[], int maxline);
void copy(char to[], char from[]);
/* print longest input line */
main()
{
int len; /* current line length */
int max; /* maximum length seen so far */
char line[MAXLINE]; /* current input line */
char longest[MAXLINE]; /* longest line saved here */
max = 0;
while ((len = getline(line, MAXLINE)) > 0)
if (len > max) {
max = len;
copy(longest, line);
}
if (max > 0) /* there was a line */
printf("%s", longest);
return 0;
}
/* get line: read a line into s, return length */
int getline(char s[], int lim) //
{
int c, i;
for (i=0; i<lim-1 && ((c=getchar()) != EOF) && c != '\n' ; ++i)
s[i] = c;
if (c == '\n') {
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
/* copy : copy 'from' into 'to'; assume to is big enough */
void copy(char to[], char from[])
{
int i;
i = 0;
while ((to[i] = from[i]) != '\0')
++i;
}
My question is about the getline function. Now looking at this following output from my command line as a reference:
me#laptop
$ characterarray.exe
aaaaa
a
aaaaaa
^Z
aaaaaa
me#laptop
$
When I type in the first character which is 'a', does the character 'a' go through the for loop, in the getline function, and initialize s[0] - s[998] = a ? And my second part of the question is once the program leaves the for loop and goes to the
s[i] = '\0'
return i;
wouldn't it be initialized s[998] = '\0' and the return integer is 998? I've spent over an hour staring at this problem and I can't seem to grasp what is going on.
Character 'a' does not go through the for loop. Look here:
for (i=0; i<lim-1 && ((c=getchar()) != EOF) && c != '\n' ; ++i)
c = getchar() is where your program stops and waits for user input -- not just once, but in a loop! This is a condition of continuing the for loop, and each time it is checked, a getchar() is called. The loop goes only one char at a time. Each time a char is added to the current end of an array (indicated by i) and the loop breaks when a newline symbol is entered or when a limit size is reached. This is very compact code but it is kind of oldschool C coding which is not very readable these relaxed days. It can be broken into following parts:
i = 0;
while(i < lim - 1) {
c = getchar();
if (c == EOF || c == '\n') break;
s[i] = c;
++i;
}
s[i] = '\0';
return i;
(also I don't think a newline symbol is needed to be included in the string, so I omitted the
if (c == '\n') {
s[i] = c;
++i;
}
part.)
Here is how the input is working in the for-loop.
Before the for-loop, the array s is empty(ish. It's full of whatever garbage is in that memory). If you type in a single a and hit enter, the for-loop goes through 2 characters 'a' and '\n'. For 'a', the variable c becomes 'a' from getchar() in the for-loop parameters, and that gets saved in the i spot (which is 0) of s. So, the array s is now
s[0] = 'a' and the rest of s has random garbage in it.
Then c becomes '\n' from getchar(). This stops the for-loop because of the c != '\n' check. The if-statement has s[i], where i is 1, become '\n' and i bumps up to 2.
Now s is
s[0] = 'a'
s[1] = '\n'
getline finishes up by making s[2] be '\0', which is the end of string character, and returns i.
Your end result is
s[0] = 'a'
s[1] = '\n'
s[2] = '\0'
and i, your length, is 2.
At page29 of 'The C programming language' (Second edition) by K&R I've read a procedure I think is broken. Since I'm a beginner I would expect I'm wrong though I cannot explain why.
Here's the code:
#include <stdio.h>
#define MAXLINE 1000 // Maximum input line size
int get1line(char line[], int maxline);
void copy(char to[], char from[]);
// Print longest input line
int
main()
{
int len; // Current line lenght
int max; // Maximum lenght seen so far
char line[MAXLINE]; // Current input line
char longest[MAXLINE]; // Longest line saved here
max = 0;
while ((len = get1line(line, MAXLINE)) > 0)
if (len > max) {
max = len;
copy(longest, line);
}
if (max > 0) // There was a line to read
printf("Longest string read is: %s", longest);
return 0;
}
// `get1line()` : save a line from stdin into `s`, return `lenght`
int
get1line(char s[], int lim)
{
int c, i;
for (i = 0; i < lim -1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = c;
if (c == '\n') {
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
// `copy()` : copy `from` into `to`; assuming
// `to` is big enough.
void
copy(char to[], char from[])
{
int i;
i = 0;
while ((to[i] = from[i]) != '\0')
++i;
}
My perplexity is: we're using the function get1line and suppose that at the end of the for-loop i is set at lim -1. Then the following if-statement will update i at lim, causing the next instruction (the one which puts the NULL character at the end of the string) to corrupting the stack (since s[lim] is not allocated, in that case).
Is the code broken?
Summary: It's not possible to exit the loop with both i == lim-1 and c == '\n', so the case you are worried about never arises.
In detail: We can rewrite the for loop (while preserving its meaning) to make the order of events clear.
i = 0;
for (;;) {
if (i >= lim-1) break; /* (1) */
c = getchar();
if (c == EOF) break; /* (2) */
if (c == '\n') break; /* (3) */
s[i] = c;
++i;
}
At loop exit (1) it can't be the case that c == '\n' because if that were the case then the loop would have exited at (3) the previous time around.*
At loop exits (2) and (3) it can't be the case that i == lim-1 because if that were the case then the loop would have exited at (1).
* This depends on lim being at least 2, so that there was in fact a previous time around the loop. The program only ever calls get1line with lim equal to MAXLINE, so this is always the case.**
** You could make make the function safe when lim is less than 2 by initializing c to a value other than '\n' before the loop begins. But if you are concerned about this possibility, then you might also want to be concerned about the possibility that lim is INT_MIN, so that lim-1 results in undefined behaviour due to integer overflow.
The code is wrong if lim == 0 because it uses c uninitialised and adds the \0.
It is also wrong if lim == 1 because it uses c uninitialised. Calling the function with lim < 2 is not very useful, but it should not fail like this.
If lim > 1 then the function is OK
for (i = 0; i < lim -1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = c;
The loop exits either if i == lim-1 or if c == EOF or if c == '\n'.
If the first condition is true (i == lim-1), then the last condition is definitely not true (unless lim < 2, as noted above).
If the first condition is false (i < lim-1), then even if the loop exits with c == \n, we know that there is space in the buffer because we know that i < lim-1.
int getline(char s[], int lim)
{
int c, i;
for(i=0; i<lim-1 && (c=getchar())!=EOF && c!='\n'; ++i)
s[i] = c;
if(c=='\n'){
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
This example is from K&R book on C, chapter 1.9 on arrays. What I do not understand is why do we have to embrace ++i inside if statement? Writing it outside should do the same work.
if(c=='\n')
s[i] = c;
++i;
s[i] = '\0'
return 0;
}
In case of embracing i program works as intended, but on the second case(which in my opinion should do the same work and this is why I edited that part) it doesn't. I ran it through debugger and watched i which in both cases was correctly calculated and returned. But program still won't work without embracing ++i. I don't get my print from printf statement, and Ctrl+D just won't work in terminal or XTerm(thorough CodeBlocks) I can't figure out why. Any hint please? Am I missing some logical step? Here is a complete code:
//Program that reads lines and prints the longest
/*----------------------------------------------------------------------------*/
#include <stdio.h>
#define MAXLINE 1000
int getline(char currentline[], int maxlinelenght);
void copy(char saveto[], char copyfrom[]);
/*----------------------------------------------------------------------------*/
int main(void)
{
int len, max;
char line[MAXLINE], longest[MAXLINE];
max = 0;
while( (len = getline(line, MAXLINE)) > 0 )
if(len > max){
max = len;
copy(longest, line);
}
if(max > 0)
printf("StrLength:%d\nString:%s", max, longest);
return 0;
}
/*----------------------------------------------------------------------------*/
int getline(char s[], int lim)
{
int c, i;
for(i=0; i<lim-1 && (c=getchar())!=EOF && c!='\n'; ++i)
s[i] = c;
if(c=='\n'){
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
/*----------------------------------------------------------------------------*/
void copy(char to[], char from[])
{
int i;
i = 0;
while( (to[i]=from[i]) != '\0')
++i;
}
/*----------------------------------------------------------------------------*/
The line
if(c == '\n')
is equivalent to
if(c != EOF)
Does that help explain why the embracing occurs?
There is a logic there:
if(c=='\n'){
s[i] = c;
++i;
}
It means only if you read an additional newline, you need to increment i one more in order to keep space for the \0 character. If you put ++i outside the if block. it means that it will always increase i by 1 even there is no newline input, in this case, since i is already incremented in the for loop , there is already space for \0, therefore, ++i again will be wrong. You can print the value of i and see how it works.
The index specified by i is the location where the terminating null should be placed when there is no more input for the line. The location just before the index i contains the last valid character in the string.
Keep in mind that the loop that reads data from stdin can terminate for reasons other than reading a \n character.
If you had this construct:
if(c=='\n')
s[i] = c;
++i;
then if the last character read from stdin wasn't a newline you would increment the index by one without writing anything into the location specified by the pre-incremented value of i. You would be effectively adding an unspecified character to the result.
Worse(?), if the for loop terminated because of the i<lim-1 condition you would end up writing the terminating null character after the specified end of the array, resulting in undefined behavior (memory corruption).
The ++i is inside the if statement because we do not want to increment i if we are not placing the \n character in the current index; that would result in leaving an index in between the last character of the input and the \0 at the end of the character string.
For loop can exit due to 3 conditions
1. reading char limit reached or EOF encountered
2. New Line encountered
For first Case We need to store Null into string s , as i points to next position to last valid character read so no need to increment i.
But for second case , as i points to next position to last valid character read , we now store newline at that position then increment i for storing NULL character.
Thats why we need to increment i in 2nd case not in 1st case.
if(c=='\n'){
s[i] = c;
++i;
}