K&R, find longest line, same code but not working? - c

I've just started to read K&R and on pages 32-33, there is a code that
finds the longest line among the inputs.
I nearly completely copy-pasted the code given in the book, just added some comment lines to make the code more understandable for me. But it isn't working.
Edit: I'm sorry for bad questioning. It seems the program does not act properly when I press Ctrl + Z, in order to terminate it. No matter how many lines I type and how many times I press Ctrl + Z, it just does nothing.
The following is my version of the code:
/* Find the longest line among the giving inputs and print it */
#include <stdio.h>
#define MAXLINE 1000 /* maximum input line length */
int getLine(char line[], int maxLine);
void copy(char to[], char from[]);
int main(void) {
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;
/* getLine function takes all the input from user, returns it's size and equates it to the variable len
* Then, len is compared whether it's greater than zero because if there's no input, no need to do any calculation
* EDGE CASE
*/
while ((len = getLine(line, MAXLINE)) > 0)
/* If the length of input is larger than the previous max length, set max as the new length value and copy that input */
if (len > max) {
max = len;
copy(longest, line);
}
if (max > 0) /* there was a line, EDGE CASE */
printf("%s", longest);
return 0;
}
/* Read a line into s, return length.
* Since the input length is unknown, there should be a limit */
int getLine(char s[], int lim) {
int c, i;
/* The loop's first condition is whether the input length is below the limit. EDGE CASE
* If it's not, omit the rest because it would cause a BUFFER OVERFLOW. Next, take the input as long as it's not an EOF command.
* Finally, if the input is end of line, finish the loop, don' take it.
*/
for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; i++)
s[i] = c;
if (c == '\n')
s[i++] = c;
s[i++] = '\0'; // always put a '\0' character to a string array ending, so that the compiler knows it's a string.
return i;
}
void copy(char to[], char from[]) {
int i = 0;
// This loop is readily assigns all chars from the source array to the target array until it reaches the ending char.
while ((to[i] = from[i]) != '\0')
++i;
}
Thanks in advance!

Okay, here's the error:
s[i++] = '\0'; // always put a '\0' character to a string array ending, so that the compiler knows it's a string.
This will cause it to terminate the string even for no input (when it got EOF directly), and since it increments i before returning it, getLine() will never return 0 and thus main() will never stop. Removing the ++ fixed it, for my simple test.
Also, the comment is misleading, the compiler doesn't know anything. The compiler is no longer around when the code runs; the in-memory format of strings is something that's needed to keep the run-time libraries happy since that's what they expect.

Related

Changing a Char to another specified Char in a specified position structure

I need to change a specified characters eg: a->z, d->b in a string. for eg: input is abandoned; output is zbznboneb. This is my code.
typedef struct {
char source;
char code;
} Rule;
void encodeChar (Rule table[5], char *s, char *t);
int main ()
{
char s[80], t[80];
Rule table[5] = { 'a', 'd', 'b', 'z', 'z', 'a', 'd', 'b', '\0', '\0'
};
printf ("Source string: \n");
gets (s);
encodeChar (table, s, t);
printf ("Encoded string: %s\n", t);
return 0;
}
void encodeChar (Rule table[5], char *s, char *t)
{
int j = 0;
while (s[j] != '\0') {
if (s[j] == 'a')
putchar ('d');
if (s[j] == 'b')
putchar ('z');
if (s[j] == 'z')
putchar ('a');
j++;
}
return 0;
}
But it gives me output dz, only and does not return the whole word when, i type abandoned.
In addition to, and in agreement with, the other answer, there are fundamentals you are missing.
When you get into encodeChar, what parameter are you using to tell you how many elements table has? (you do want to iterate over each element in table checking table[i].source against each character to determine if a substitution is needed, right?)
note: C generally uses all lowercase for variable and function names while reserving all uppercase for constants and macros. C avoids the use of camelCase and MixedCase names -- leave those for C++ or java. While it is a matter of style, so it is largely up to you, it does say a lot about your appreciation for C, just like using gets does....
Don't use magic numbers in your code. If you need a constant, (e.g. 80), declare one at the top of your code, e.g.
#define MAXC 80 /* maximum characters for input buffer */
If you have multiple constants to declare, using an enum is an orderly way to declare global constants.
Using constants prevents having to pick through multiple array declarations to change their size. There is one convenient place up top to make the change.
Don't use gets, it is helpless against buffer overrun and has been removed from C11. Use fgets. All valid line-oriented input functions (e.g. fgets, and POSIX getline) read and include the trailing '\n' in the buffers they fill with input. Therefore, you need to trim the trailing newline from input, or you will have '\n' dangling off the end of any string you store that can cause problems with comparisons, etc.. Simply get the length and check character length - 1 to validate it is a '\n', then just overwrite it with a nul-terminating character ('\0' or 0, they are equivalent)
It's quite simple, e.g.:
len = strlen (s); /* get length of s */
if (len && s[len - 1] == '\n') /* check for \n */
s[--len] = 0; /* overwrite with \0 */
note: by using --len you now have the new length preserved in len.
Finally, for your encodechar function, you need to know how many elements of table you have. For each character in s you will compare it to each table[i].source if a match is found, then you will assign table[i].code to t and go to the next character. If not found, you will simply assign the character in s to t.
note: there is no need for the 5th element of table (e.g. '\0', '\0'), you can easily nul-terminate t without it -- and it is not a replacement.
Putting that together, you could write encodechar similar to the following:
void encodechar (rule *table, size_t sz, char *s, char *t)
{
size_t i;
while (*s) { /* for each character */
int replaced = 0; /* replaced flag */
for (i = 0; i < sz; i++) /* for each element of table */
if (*s == table[i].source) { /* is char == table[i].source */
*t = table[i].code; /* replace it */
replaced = 1; /* set replaced flag */
break; /* get next character */
}
if (!replaced) /* if not replaced */
*t = *s; /* copy from s to t */
s++, t++; /* increment s and t */
}
*t = 0; /* nul-terminate t */
}
Putting it altogether, and noting main() is type int and therefore returns a value (See: C11 Standard §5.1.2.2.1 Program startup (draft n1570). See also: See What should main() return in C and C++?), you can do something similar to the following:
#include <stdio.h>
#include <string.h>
#define MAXC 80 /* maximum characters for input buffer */
typedef struct {
char source;
char code;
} rule;
void encodechar (rule *table, size_t sz, char *s, char *t);
int main (void) {
char s[MAXC] = "", t[MAXC] = "";
rule table[] = { {'a', 'd'}, {'b', 'z'}, {'z', 'a'}, {'d', 'b'} };
size_t len = 0, n = sizeof table/sizeof *table;
printf ("Source string : ");
if (!fgets (s, MAXC, stdin)) {
fprintf (stderr, "error: invalid input.\n");
return 1;
}
len = strlen (s); /* get length of s */
if (len && s[len - 1] == '\n') /* check for \n */
s[--len] = 0; /* overwrite with \0 */
encodechar (table, n, s, t);
printf ("Encoded string: %s\n", t);
return 0;
}
void encodechar (rule *table, size_t sz, char *s, char *t)
{
size_t i;
while (*s) { /* for each character */
int replaced = 0; /* replaced flag */
for (i = 0; i < sz; i++) /* for each element of table */
if (*s == table[i].source) { /* is char == table[i].source */
*t = table[i].code; /* replace it */
replaced = 1; /* set replaced flag */
break; /* get next character */
}
if (!replaced) /* if not replaced */
*t = *s; /* copy from s to t */
s++, t++; /* increment s and t */
}
*t = 0; /* nul-terminate t */
}
Example Use/Output
$ ./bin/encode
Source string : abcdefghijklmnopqrstuvwxyz
Encoded string: dzcbefghijklmnopqrstuvwxya
Always compile with warnings enabled, and do not accept code until it compiles cleanly without warning. To enable warnings add -Wall -Wextra to your gcc compile string. (add -pedantic for several additional warnings). For VS (cl.exe on windoze), add /Wall. For clang, add -Weverything. Read and understand each warning. They will identify any problems, and the exact line on which they occur. You can learn as much about coding by simply listening to what your compiler is telling you as you can from most tutorials.
Look things over and let me know if you have further questions.
If you'd be so kind as to explain where you output any character other than a, z or d within this code:
void encodeChar(Rule table[5], char *s, char *t)
{
int j =0;
while(s[j] != '\0')
{
if (s[j] == 'a')
putchar('d');
if (s[j] == 'b')
putchar('z');
if (s[j]=='z')
putchar('a');
j++;
}
return 0;
}
Please show me where you putchar anything but 'a', 'd' or 'z' and you'll get some strong hints towards the answer to your question.
P.S. Most importantly, if my rather educated guess was correct, YOU NEED A BOOK! You can't safely learn C without one. It's not theoretically impossible, but it is practically unlikely that you'll get it right without wasting heaps of time writing really unstable code.
I recommend K&R2E. It's not beginner friendly in the sense that it assumes you know some other programming concepts, but then again, C is not a beginner friendly language in the sense that you can't just mash your face against a keyboard and win.
If you're looking for something beginner friendly, choose a different language. C doesn't yet have any beginner friendly resources, or any reputable teachers that start from a beginner level, to the best of my knowledge.
If you're going to program in C, it is expected that you be able to read... books, manuals and error messages from compilers and valgrind all need the same kind of attention to detail that your code needs.
Anything you do manage to learn without a book, you're likely going to have to unlearn. So stop guessing immediately, start reading immediately and hopefully next time I hear from you it's not about something basic and explained by the book.

Programmes won't run on Ubuntu 12.04 and Windows XP

Having installed Eclipse and CB, I encountered several projects that do not start properly. I assumed that it was because of the OS I used, that is why I switched to Ubuntu. However, some programmes I had tried to run still would not work correctly. For instance, this code from clc-wiki outputs nothing upon pushing Enter:
#include <stdio.h>
#define MAXLINE 40 /* maximum input line size */
int getlines(char line[], int maxline);
void copy(char to[], char from[]);
/* print longest input line */
int main()
{
int c;
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 = getlines(line, MAXLINE)) > 0) {
if (line[len-1] != '\n')
while ((c = getchar()) != EOF && c != '\n')
++len;
if (len > max) {
max = len;
copy(longest, line);
}
}
if (max > 0) { /* there was a line */
printf("Longest line with %d characters:\n", max);
printf("%s ...\n", longest);
}
return 0;
}
/* getline: read a line s, return length */
int getlines(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;
}
The similar problem is faced, if we run it in XP. Nevertheless, everything is perfect, if we compile the very same code in ideone.
gcc and g++ are installed, as well as mingw for Windows.
Could you please tell me what the problem can be all about?
this code outputs nothing upon pushing Enter
That is the correct behavior of your code. It is designed to print nothing upon pushing Enter.
That code, in particular, requires an end-of-file indication. If your program reads from a file (as is the case on ideone), the end-of-file indication happens more-or-less automatically. If your program reads from the computer keyboard (as when you run it interactively), then you must provide an end-of-file indication.
To exercise the code you provided:
On Linux, from the keyboard, enter several lines of varying lengths, each followed by Enter. Then enter CONTROL-D on a line by itself.
On Windows, from the keyboard, enter several lines of varying lengths, each followed by Enter. Then enter enter CONTROL-Z on a line by itself.

getting output from a c program

I compiled the below program:
#include <stdio.h>
#define MAXLINE 1000 /* maximum input line length */
int getline(char line[], int maxline);
void copy(char to[], char from[]);
/* print the 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;
}
/* 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;
}
/* 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;
}
I tried to run it in bash shell:
gcc -o longest-line longest-line.c
./longest-line
And basically it turns into a running process (shows in result of ps aux) and the cursor just blinks. In the code, when the program is run and the getline function is called, it does 1000 iterations and the getchar is called each time to get input from the terminal in order to increment the counter if it's not end of file or newline. However, immediately there is no input in the terminal and when I start adding input and press the enter key:
$ ./longest-line
Hello World
Hello Again
Nothing happens. It's supposed to print the longest line.
The problem is that if you press '\n' from keyboard getline will always return 1 because of this statement
if (c == '\n') {
s[i] = c;
++i;
}
and the line while ((len = getline(line, MAXLINE)) > 0) will always be true.
But if you use a file as standard input it will work fine because of the EOF.
If you want it to work from keyboard input press Ctrl-D or Ctrl-Z to simulate an EOF.
So, on my compiler, I had to fix a few minor issues.
Generally, main should be written as int main() { ... } or int main(int argc, char **argv) { ... }.
getline() conflicts with a builtin function that gets pulled in from the #include <stdio.h>, so I simply renamed yours to getline_custom and also renamed all the usage points.
That being said, with these minor fixes, (which may not be required under your compiler), your program works correctly.
I believe your confusion is that the program won't print the longest line until after you've sent EOF. In bash, you can do this with CTRL+D.
Example:
[12:39pm][wlynch#watermelon /tmp] ./foo
test // Then I hit enter
longest line // Then I hit enter
short line // Then I hit enter
one more // Then I hit ctrl-D
longest line // This is output from the program.
Another Example:
If we use redirection, we can more easily see the difference between the input and output.
[12:42pm][wlynch#watermelon /tmp] printf "longest line\nshort line" | ./foo
longest line
or, using an input file:
[12:53pm][wlynch#watermelon /tmp] cat input.txt
longest line
short line
foo
blah
[12:53pm][wlynch#watermelon /tmp] cat input.txt | ./foo
longest line
On the other hand
If, on the other hand, you'd like the program to print the current longest line after each line of input, then we would need to change the code in this program.
Here's an example of that:
int main() {
int len = 0; /* current line length */
int max = 0; /* maximum length seen so far */
char line[MAXLINE]; /* current input line */
char longest[MAXLINE]; /* longest line saved here */
while ((len = getline(line, MAXLINE)) > 0) {
if (len > max) {
max = len;
copy(longest, line);
}
printf("Current longest line: %s", longest);
}
}

What condition is causing this program to leave the for loop early due to a EOF character?

Go ahead and look to the bottom of this post for the actual question
I may be trying to understand the really simple things a bit too much, but I just cannot continue until I figure this out.
While reading K&R 1.9 Character Arrays, you come across an example program which takes input and determines which line was the longest, then it reprints it.
In order to understand the getline function of this program, I have modified it slightly to print some information during its progression. Now I have found some input which, once I understand, should greatly enhance my understanding of getchar().
On a side note, when I try to read source code from the libraries I get completely overwhelmed. For example I tried hunting down getchar() and ended up staring dumbfounded at a tower of 18 #endif somewhere on line 300ish of stddef.h. I am going to save that headache by discontinuing the pursuit of functions in stdio.h for the near future. Is it normal that the libraries are extremely hard to find something? Shouldn't the function exist as readable source, or do I just have my hopes up?
I will just go ahead and post the entire modified program from K&R for convenience sake:
#include <stdio.h>
#define MAXLINE 5 /* maximum input line length */
int getline(char line[], int maxline);
void copy(char to[], char from[]);
/* print the 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\n", longest);
printf("INFO: max = %d\n", max);
printf("INFO: sizeof s[] = %d", sizeof longest);
return 0;
}
/* 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;
printf("INFO: s[%d] = %c\n", i, c);
if (c == '\n') {
printf("INFO: %d\n", i);
printf("INFO: s[%d] = \\n\n", i);
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;
}
Here is some example input/output: I am using CodeBlocks and Windows 7
Finally, my question is why does the for loop in the getline() function leave the loop after it gets to F and therefore places the null terminator after F as \0
My hypothesis is that perhaps putting the EOF character shown as ^Z in the windows terminal, the array is truncated to a length of 4 characters plus a null terminator. It turns out if I put the EOF character inside a line to be read, the saved line s[] will only contain a maximum size of 5. What also confuses me is that the variable len will always be 0, is that because len is assigned during the while loop in main(), and does not have its value saved once that while loop is broken?
Something interesting to me, but probably not to experienced C users, is that the if statement right after the for loop in getline() can either be inside the for loop, or outside and it does not change the program. Interesting, because won't that mean that whether or not the for loop executes any code in the body, at the very least (c=getchar()) is executed?
Sorry the question is so wordy, I appreciate any helpful comments/answers.
Because MAXLINE is 5, and the getline function never reads more than MAXLINE - 1 characters.

small C program doesnt work

I tried to make a program that gets a user input(lines) and prints the longest line that is over 80 characters long. I made the program , but when i ran it , it outputed some very weird symbols. Could you please tell me what might be wrong with my code ?
#include <stdio.h>
#define MINLINE 80
#define MAXLINE 1000
int getline(char current[]);
void copy(char from[], char to[]);
int main(void)
{
int len; // current input line lenght
int max; // the lenght of a longest line that's over 80 characters
char current[MAXLINE]; // current input line
char over80[MAXLINE]; // input line that's over 80 characters long
while (len = (getline(current)) > 0) {
if (len > MINLINE) {
max = len;
copy(current, over80);
}
}
if (max > 0) {
printf("%s", over80);
}
else {
printf("No input line was over 80 characters long");
}
return 0;
}
int getline(char current[]) {
int i = 0, c;
while (((c = getchar()) != EOF) && c != '\n') {
current[i] = c;
++i;
}
if (i == '\n') {
current[i] = c;
++i;
}
current[i] = '\0';
return i;
}
void copy(char from[], char to[]) {
int i = 0;
while ((to[i] = from[i]) != '\0') {
++i;
}
}
Thank you very much for your help !
max can be not initialized if no long line is found. Using it in if (max > 0) is then undefined behavior.
This line:
while (len = (getline(current)) > 0) {
assigns the value of (getline(current)) > 0) to len, which is not what you want (len will be 0 or 1 afterwards.
EDIT: Just saw AusCBloke's comment, you should also check for both len > max and len > MINLINE or you'll just get the latest line longer than 80 chars, not the longest overall line.
You should also initialize max to 0, so it should be
max = 0;
while ((len = getline(current)) > 0) {
if ((len > MINLINE) && (len > max)) {
Other minor errors/tips:
The built in functions strcpy and strncpy do what your copy function does, there's no need to reinvent the wheel.
In your getline function, use MAXLINE to prevent buffer overflows.
Assuming that this is a homework, here's a hint: this piece of code looks very suspicious:
if (i == '\n') {
current[i] = c;
++i;
}
Since i represents a position and is never assigned a character, you are effectively checking if the position is equal to the ASCII code of '\n'.
Your copy method doesn't null terminate the string:
void copy(char from[], char to[]) {
int i = 0;
while ((to[i] = from[i]) != '\0') {
++i;
}
to[i] = '\0'
}
which probably explains the weird characters being printed.
You could use the builtin strcpy() to make life easier.
I can't test your code right now, but it may be caused by character arrays not being cleaned. Try memset-ing the char arrays to 0.
If you supply input data that has lines with more than 1000 characters you will overflow your fixed size buffers. By feeding in such input I was able to achieve the following output:
╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠
╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠
╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠
There are a number of problems with your code. Mostly they are due to wheel-reinvention.
int getline(char current[]);
You don't need to define your own, getline(), there is already one in stdio.h.
void copy(char from[], char to[]);
There are also a number of functions for copying strings in string.h.
It's also a good idea to initialise all 0f your variables, like this:
int len = 0; // current input line length
...this can prevent problems later, like comparisons to max when you haven't initialised it.
If you initialise max like this...
int max = MINLINE; // the length of a longest line that's over 80 characters
...then it's easier to do the length comparison later on.
char* current = NULL;
size_t allocated = 0;
If current is NULL, then getline() will allocate a buffer for storing the line, which should be freed by the user program. getline() also takes a pointer to a size_t, which contains the amount of bytes needed to store the line.
while (len = (getline(current)) > 0) {
Should be replaced by the following...
while ((len = getline(&current, &allocated, stdin)) > 0) {
...which updates and compares len to 0.
Now, instead of...
if (len > MINLINE) {
...you need to compare with the last longest line, which we initialised earlier...
if (len > max) {
...and then you're good to update max as you were...
max = len;
Where you called your copy() use strncpy(), which will prevent you writing over 1,000 characters into the allocated buffer:
strncpy(over80, current, MAXLINE);
Because we initialised max, you'll need to change your check at the end from if (max > 0) to if (max > MINLINE).
One more tip, changing the following line...
printf("No input line was over 80 characters long");
...to...
printf("No input line was over %d characters long", MINLINE);
...will mean that you only have to change the #define at the top of the file to increase or decrease the minimum length.
Don't forget to...
free(current);
...to prevent memory leaks!

Resources