0

I am learning C. What I want my program to do is prompt for three variables compare them to specific values using if statement and return true and print a welcome message only if (all) comparisons are true. Any thing else return false and print a sorry message. But when I compile and run the code, which is shown below.

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

int main ()
{
    int years;
    char name[15];
    char gender[7];
    char evaluation[7];
    char age[2];
    printf ("enter name, please     ");
    fgets (name, 15, stdin);
    printf ("enter gender, please     ");
    fgets (gender, 7, stdin);
    printf ("enter evaluation, please     ");
    fgets (evaluation, 20, stdin);
    printf ("Please enter age      ");
    fgets (age, 2, stdin);

    years = strlen (age);

    {
        if ((strcmp (gender, "male") == 1)
            && (strcmp (evaluation, "good") == 1) && years < 50)

            printf ("welcome volunteer, %s . You're fit to participate\n",
                    name);

        else
            printf ("Sorry, %s. You're not fit to participate/n", name);
    }

    return (0);
}

-It compiles with no errors and no warnings. But, - the if statement returns true if any of the first two comparisons, gender and evaluation, is true.

but, - it returns false when all are false. and - when the first two comparisons ,gender and evaluation,are both false and the third comparison ,age, is true it returns false.

I would like to know what's wrong with my if statement. Thanks for your help

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
Allan Mayers
  • 185
  • 1
  • 2
  • 8
  • `strcmp` returns `0` if and only if the two input strings are identical. What exactly are you trying to achieve by comparing its output to `1`? A good way to start learning C, is by checking the documentation of any standard function before using it. A simple search on Google would have solved the problem. – barak manos Aug 24 '16 at 18:14
  • I thought the very opposit i thought it returns 1 if they were identical. – Allan Mayers Aug 24 '16 at 18:17
  • 3
    `fgets` retains the `newline` you typed at the end of the string it writes to. The `newline` is not present in the comparison strings. Furthemore, `strcmp` returns `0` when the strings match. [Please see this](http://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input). – Weather Vane Aug 24 '16 at 18:19
  • I changed to 0 and it did the opposit of what i wanted it gives the sorry message when i give all the right inputs – Allan Mayers Aug 24 '16 at 18:20
  • `years = strlen(age);` does not give the age. `years = atoi(age);` does. – Weather Vane Aug 24 '16 at 18:21
  • Statements don't return anything. Functions return a result. Always read the documentation for functions you use. – too honest for this site Aug 24 '16 at 18:22
  • Format&indent your code properly. And `return` is not a function, but a statement. Dont use parentheses around simple expressions. They are missleading and potentially error-prone. – too honest for this site Aug 24 '16 at 18:23
  • See ["How to remove the newline when using fgets"](https://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input). Also, it's generally a bad idea to use small buffers with `fgets`. Instead, declare a line buffer, e.g. `char line[200]`. Read the string into the line buffer with `fgets`, and then copy it to the smaller buffer after checking the length and removing the newline. – user3386109 Aug 24 '16 at 18:25
  • 1
    @AllanMayers, style is up to you, but while you are learning a *4-space* indent can help keep things organized. – David C. Rankin Aug 24 '16 at 18:25
  • Thanks very much every one, Regarding the editing I have already edited the post and I will look at the edits you made and approve one of them. I still have one problem , if you could help me, it's with the integer variable age it's always true in the if statement. – Allan Mayers Aug 24 '16 at 18:51

4 Answers4

2

strcmp doesn't work the way you think it works. It returns 0 (not 1) if the strings are the same, and a value less than one (which is usually -1, but do not rely on it) if the first has a lower dictionary sorting than the second string, and a value larger than 0 (which may or may not be 1) if the second is higher.

So when you checked for equality of strings, what you actually checked for was for a very specific inequality of strings. They is probably the reason your program misbehaves.

In general, whenever a function returns unexpected results, make sure to go over that function's documentation to make sure you are not misusing it.

Shachar Shemesh
  • 8,193
  • 6
  • 25
  • 57
1

You're not hitting your conditions here because you're expecting a different return from strcmp when you get a match.

From the strcmp manpage:

The strcmp() and strncmp() functions return an integer less than, equal to, or greater than zero if s1 (or the first n bytes thereof) is found, respectively, to be less than, to match, or be greater than s2.

Try testing for negation on the strings used in strcmp.

if (!strcmp(gender_str, "male"))
tijko
  • 7,599
  • 11
  • 44
  • 64
1

You have a few things wrong. As others have said strcmpshould return 0 if the strings are equal.

Second thing, remove the newline character after you read in the values

 gender[strlen(gender) - 1] = '\0';
 evaluation[strlen(evaluation) - 1] = '\0';

Third, years = strlen (age); should be years = atoi(age)

FreeStyle4
  • 272
  • 6
  • 17
  • I Implemented or your advice and all errors are fixed expect one , and that is when age is greater than 50 it still returns true and displays welcome message even though it should return false and display the sorry message. – Allan Mayers Aug 24 '16 at 18:40
  • You have `fgets(evaluation, 20)`, but `evaluation` is declared to only hold 7 characters. If you type more than 6 characters, you'll write outside the array bounds and cause undefined behavior. – Barmar Aug 24 '16 at 18:43
  • Thanks a lot , fixed it. But still the same problem with the age variable. it always returns true. – Allan Mayers Aug 24 '16 at 18:47
  • @AllanMayers `char age[2];` is not big enough for people over the age of 9. There is room for one digit and one `nul` terminator. Let alone the `newline`. Don't be mean with your string allocations. – Weather Vane Aug 24 '16 at 18:49
  • @AllanMayers, yeah just change char age[2] to something higher, and change it on the fgets as well – FreeStyle4 Aug 24 '16 at 18:52
  • Just to add to that. If `fgets` is not given enough room to read a whole line, the line will be broken, and no `newline` will be appended to the input string. The rest of the line is not discarded, it will be read as the *next* line. One reason the `newline` is included in the read, is so that the program can know it has read the *whole* line. – Weather Vane Aug 24 '16 at 18:57
  • You're amazing, guys. I gave age more room and It worked like a charm. – Allan Mayers Aug 24 '16 at 19:00
  • Although uncommon, after `fgets()`, `gender[0]` may be 0 and `gender[strlen(gender) - 1]` is a problem. Defensive coding insures the first character after `fgets()` was not a user entered null character. Suggest `gender[strcspn(gender, "\n")] = '\0';` – chux - Reinstate Monica Aug 24 '16 at 20:07
1

By trying to limit the buffer size to just what will fit, you are unwittingly shooting yourself in the foot. For example char gender[7]; will hold female\0, but what happened with the '\n' at the end of the input?

It remains unread in the input buffer (stdin here), just waiting to cause you problems on your next call to fgets (ever wonder why when you entered female your code seemed to skip the age prompt?) What happens if a cat steps on the keyboard and enters kkkkkkkkkkkkkkkkkkkkkkkkk for gender, or a nutty user enters you go figure it out!?

Lesson one, don't try and micro-manage the input array size when taking user input. Choose adequately sized buffers for your input (64, 128, or 256 chars are fine). If you are on an embedded system with very limited memory, sure, then cut back, but for normal use, a few extra bytes never hurts.

Lesson two -- always check that the full line has been read and that additional characters do not remain unread just waiting to screw you over on your next read. How do you do this? Simple, fgets reads up to and including the next '\n' (or the maximum number of characters specified in size, including the nul-byte). If you check the last character and it isn't a '\n', you have experienced a short-read and there are additional characters in the input buffer that remain unread.

Lesson three -- fgets reads up to and including the next '\n'. What do you think happens when you compare:

if (strcmp ("male\n", "male") == 0)

You will need to check for, and remove the '\n' from the end of the input if it is present.

How? Since this is something you will be doing for every input, it would make sense to have a single function you could call that would check if the newline was present at the end of the input and if so, overwrite the '\n' with a nul-terminating character (which is simply 0 or the equivalent '\0' form). If the '\n' isn't present in your input, you know it remains unread in the input buffer, so you need to read & discard all remaining characters in that line of input if the remainder is unneeded or read & process the input if it is. You can do that simply with something like:

void check_eol (char *s)
{
    if (!s || !*s) return;               /* validate pointer & non-empty s */
    if (*s == '\n') { *s = 0; return; }  /* avoid strlen call if 1st is \n */

    size_t len = strlen (s);     /* get lenth and check if last char is \n */
    if (s[len - 1] == '\n')
        s[len - 1] = 0;             /* if so, overwrite '\n' with nul-char */
    else {            /* otherwise '\n' remains in input buffer, read rest */
        int c;        /* of line, to prevent failure on next call to fgets */
        while ((c = getchar()) != '\n' && c != EOF) {}
    }
}

(you can add a FILE * parameter and use fgetc in place of getchar() if you are reading from other than stdin)

Putting all the pieces of the puzzle together, and leaving your age and gender arrays small to illustrate the point, along with a few other tips, you can do something like the following with your code:

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

/* use constants instead of 'magic' numbers in your code.
 * since you know the limit of age and gender, you can tailor
 * the size, otherwise, it is better to create a buffer large
 * enough to handle any name or evaluation.
 */
enum { MAXA = 4, MAXG = 8, MAXE = 32, MAXC = 256 };

void check_eol (char *s);

int main (void) {

    /* int years = 0; */
    char name[MAXC] = "",      /* initializing char arrays all 0 can insure */
        gender[MAXG] = "",     /* nul-terminated strings if you forget :)   */
        evaluation[MAXE] = "",
        age[MAXA] = "";

    printf ("enter name, please (max: %3d)       : ", MAXC - 1);
    fgets (name, MAXC, stdin);
    check_eol (name);

    printf ("enter gender, please (max: %3d)     : ", MAXG - 1);
    fgets (gender, MAXG, stdin);
    check_eol (gender);

    printf ("enter evaluation, please (max: %3d) : ", MAXE - 1);
    fgets (evaluation, MAXE, stdin);
    check_eol (evaluation);

    printf ("Please enter age (max: %3d)         : ", MAXA - 1);
    fgets (age, MAXA, stdin);
    check_eol (age);
    if (*age < '0' || '9' < *age) {     /* make sure 1st char is a number */
        fprintf (stderr, "error: non-numeric age entered.\n");
        return 1;
    }

    // years = strlen (age);   /* WTF?? */

    if ((strcmp (gender, "male") == 0)
        && (strcmp (evaluation, "good") == 0) && *age < 5 + '0')
        printf ("\nwelcome volunteer, %s . You're fit to participate\n",
                name);
    else
        printf ("\nSorry, '%s'. You're not fit to participate\n", name);

    return (0);
}

/** check for '\n' at end of 's', overwrite with 0 if present, otherwise
 *  read remaining chars from stdin.
 */
void check_eol (char *s)
{
    if (!s || !*s) return;               /* validate pointer & non-empty s */
    if (*s == '\n') { *s = 0; return; }  /* avoid strlen call if 1st is \n */

    size_t len = strlen (s);     /* get lenth and check if last char is \n */
    if (s[len - 1] == '\n')
        s[len - 1] = 0;             /* if so, overwrite '\n' with nul-char */
    else {            /* otherwise '\n' remains in input buffer, read rest */
        int c;        /* of line, to prevent failure on next call to fgets */
        while ((c = getchar()) != '\n' && c != EOF) {}
    }
}

(note: the check for age < 50 just checks that the first character of age is less than 5, the test can simply be written *age < '5', is the way it is currently written equivalent? why/why not?)

Example Use/Output

Fit - male, good, < 50

$ ./bin/rfmt
enter name, please (max: 255)       : Somebody With Avery-Longname
enter gender, please (max:   7)     : male
enter evaluation, please (max:  31) : good
Please enter age (max:   3)         : 49

welcome volunteer, Somebody With Avery-Longname . You're fit to participate

Not fit - male, good, = 50

$ ./bin/rfmt
enter name, please (max: 255)       : Somebody With Avery-Longname
enter gender, please (max:   7)     : male
enter evaluation, please (max:  31) : good
Please enter age (max:   3)         : 50

Sorry, 'Somebody With Avery-Longname'. You're not fit to participate

Not fit - unknown, good, < 50

$ ./bin/fgets_user_input
enter name, please (max: 255)       : Somebody With Avery-Longname
enter gender, please (max:   7)     : unknown
enter evaluation, please (max:  31) : good
Please enter age (max:   3)         : 49

Sorry, 'Somebody With Avery-Longname'. You're not fit to participate

Look things over and let me know if you have any questions. Hope some of this helps.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Sure, everyone needs a little help figuring the basics out. C is a fantastic language. However with the unmatched low-level control it provides, it takes a bit of additional dedication and learning. Enjoy the ride. Good luck with your coding. – David C. Rankin Aug 24 '16 at 21:21