First -- Do not use 'gets'. It is horribly insecure. There is no limitation on the number of characters it will read or whether the size of the buffer you provide has adequate storage. That allows for buffer overrun exploits and is the primary reason it has been dropped from the C library. If your professor insists on using it -- find a new professor.
The other problem you have is failing to validate each step in your process. You fail to check if gets
actually read anything before passing the pointers to strcmp
or strlen
.
Further, your indexing is nonsense. strlen(x) - n
doesn't index the end - n
word in the buffer. For that you have to tokenize the string (split it into words). There are a number of ways to do it.
One method that works no matter what is simply finding the end of the string (e.g. strlen(line) - 1
) and using a pointer to iterate from the end of the string towards the start until your first whitespace is found (or you reach the beginning).
The C library (in string.h
) provides strrchr
which automates that process for you. It will start at the end of a string and iterate backwards until it finds the first occurrence of the character you tell it to find returning a pointer to that character (or returning NULL
it that character is not found). The only downside here is you are limited to search for a single character.
The C library (in string.h
) provides strtok
, which does not provide for a reverse search, but does provide the ability to split a string based on a set of delimiters you provide (e.g. it could handle splitting on any one of space
, tab
, '.', etc..). Here you simply store the pointers to each of the words (or a copy of the words) and take the last 3 indexes for comparison.
The following provides an example that uses strrchr
presuming your words or separated by one (or more) spaces
. Both the method used below and strtok
modify the original string, so make a copy of a string before parsing if the string is originally stored in read-only memory (e.g. a string literal).
The program expects the filename to read to be provided as the first argument (or it will read from stdin
if no argument is provided). Additional comments are provided in the code, in-line, below:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef BUF_SIZ /* if you need constants... define them */
#define BUF_SIZ 8192 /* don't put 'magic' numbers in code */
#endif
#define NLAST 3
int main (int argc, char **argv) {
size_t len = 0;
char line1[BUF_SIZ] = "",
line4[BUF_SIZ] = "",
*last[NLAST] = { NULL };
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
return 1;
}
if (fgets (line1, BUF_SIZ, fp) == NULL) { /* read 1st line */
fprintf (stderr, "error: failed to read line1.\n");
return 1;
}
for (int i = 0; i < NLAST; i++) /* read/discard lines 2,3 read line 4 */
if (fgets (line4, BUF_SIZ, fp) == NULL) {
fprintf (stderr, "error: failed to read line4.\n");
return 1;
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
len = strlen (line1); /* get length of line1 */
if (len && line1[len-1] == '\n') /* validate last is '\n' */
line1[--len] = 0; /* overwrite with nul-character */
else { /* error: handle line too long or no POSIX EOL */
fprintf (stderr, "error: line1 too long or no POSIX EOL.\n");
return 1;
}
len = strlen (line4); /* same thing for line4 */
if (len && line4[len-1] == '\n')
line4[--len] = 0;
else {
fprintf (stderr, "error: line4 too long or no POSIX EOL.\n");
return 1;
}
if (!*line1 || !*line4) { /* test if either is empty-string */
fprintf (stderr, "error: one or both line(s) empty.\n");
return 1;
}
for (int i = 0; i < NLAST; i++) { /* loop NLAST times */
char *p1 = strrchr (line1, ' '), /* get pointer to last ' ' */
*p4 = strrchr (line4, ' ');
if (!p1) { /* validate result of strrchr */
if (i < NLAST - 1) { /* if not last iteration - handle error */
fprintf (stderr, "error: only '%d' words in line1.\n", i+1);
return 1;
}
else /* if last iteration, assign line to pointer */
p1 = line1;
}
if (!p4) { /* same for line4 */
if (i < NLAST - 1) {
fprintf (stderr, "error: only '%d' words in line4.\n", i+1);
return 1;
}
else
p4 = line1;
}
/* copy to last array in order - checking if p1 is beginning of line */
last[NLAST - 1 - i] = p1 == line1 ? p1 : p1 + 1;
while (p1 > line1 && *p1 == ' ') /* nul-terminate at space */
*p1-- = 0;
while (p4 > line4 && *p4 == ' ')
*p4-- = 0;
}
printf ("\nthe last %d words in lines 1 & 4 are the same:\n", NLAST);
for (int i = 0; i < NLAST; i++)
printf (" %s\n", last[i]);
return 0;
}
Example Input File
$ cat dat/last14-3.txt
My dog has fleas
My snake has none
The cats have none
Cats are lucky because the dog has fleas
Example Use/Output
$ ./bin/lines1_4_last3 < dat/last14-3.txt
the last 3 words in lines 1 & 4 are the same:
dog
has
fleas
Regardless which method you choose to tokenize the lines, you must validate each step along the way. Look things over and make sure you understand why each validation was necessary, if not, just ask and I'm happy to help further.