C challenge #1

+1 Eric Hines · January 11, 2016
I'm up to Bucky's tutorial #33. 

Here's my code, but only part of it works. I get the else statement to print. I can't seem to get the password to work....

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

int main()
{
int upperletter;
int specialchar;
int number;
upperletter;
specialchar;
number;

printf("Enter your password. The first entry must be an upper case letter. The second must be a special character. The last must be a number.\n");
scanf("%c,%c,%c\n", &upperletter,&specialchar,&number);


if(isupper(upperletter)&&isdigit(specialchar)&&isdigit(number))
{
    printf("Thank you. %c%c%c is a valid password.\n",upperletter,specialchar,number);
}
else
{
    printf("Your password does not meet the requirements.\n");
}
return 0;
}

Post a Reply

Replies

- page 1
Oldest  Newest  Rating
0 Laura Lee · January 11, 2016
You do not need all those headers.
You do not need to use scanf on your conditions, that doesn't make sense.
You are meant to retrieve a string passcode from the user then check all the conditions yourself.
Also avoid using scanf and use fgets instead.
Checking every condition at once isn't such a good idea, since you could not specifically provide under which condition the passcode failed.
+1 Linguist Llama · January 11, 2016
Greetings. I don't entirely agree that scanf should be avoided entirely in favour of fgets, as there are operations that scanf can perform that fgets can't. However, I do agree that in this case fgets is the more suitable tool for the job.

Having said that, it's also important to face the issues you're facing when you use scanf. First and fore-most, and this goes for the majority of C-standard functions (e.g. `fopen`, `fgets`, `malloc`), your logic needs to CHECK THE RETURN VALUE... For example:

int n = scanf(...);


If you use scanf to read one value, you can expect a successful call to scanf to return 1, so n will be 1 in that case. If you use scanf to read two values, n should be 2, or else you can't rely upon both of the values to be determinate. Thus your logic should probably look something like this:

unsigned char upperletter, specialchar, number;
int n = scanf("%c,%c,%c", &upperletter, &specialchar, &number);
if (n == 3) { /* You're good to go! All three characters can be used safely */ }
if (n == 2) { /* Only upperletter and specialchar can be used safely */ }
if (n == 1) { /* Only upperletter can be used safely */ }
if (n == 0 || n == EOF) { /* None of those variables can be used safely. */ }


About the commas in your format string "%c,%c,%c\n", that probably doesn't mean what you think it does. This is telling scanf to expect that the user will enter a comma between the characters, so you'll probably notice scanf returns 1 when you enter input like "abc" instead of "a,b,c".

The \n doesn't mean what you might think it means, either. A single whitespace character of any kind (space, tab, newline, etc) will cause scanf to read and consume as many whitespace characters as possible, so you might notice \n also causes spaces to be discarded.

Secondly, take note of warnings. Your compiler is likely warning you that the type of your scanf arguments are conflicted. On the one hand, when you give `%c` to scanf you tell scanf the argument will be a character pointer. On the other hand, all of your arguments are int pointers. As the C standard says at < http://port70.net/~nsz/c/c11/n1570.html#7.21.6.2p10 >

> ... If this object does not have an appropriate type, or if the result of the conversion cannot be represented in the object, the behavior is undefined.

The appropriate types in this case are char, unsigned char or signed char. Not int.

I'd suggest using `unsigned char`, if you continue to use scanf for this for whatever crazy reason. The reason is that isupper and isdigit are only defined to operate on unsigned char values and the EOF value; any other values will produce undefined behaviour. It's much more compatible with getchar.

It is unfortunate that most people habitually use any and all of these input/output functions incorrectly. Read the manuals and be well informed...
+2 Laura Lee · January 11, 2016
Scanf might be useful for formatted strings, but it is recommended to input all data as strings and convert them yourself.
I try to avoid it whenever I can and now I never really have the need for it. Scanf does not provide a buffer size for input and could cause beginners to buffer overflow. Scanf also does not clean the input stream. There can be cases where fgets also can't clean the stream in cases where the input provided exceeds the size of the buffer, but that is fairly common sense. I have a habit of clearing the input stream myself each time I use it.
+1 Linguist Llama · January 11, 2016
Hi, Laura. I hope you will let me help you find some food for thought today... As you're reading this, try to understand that I wouldn't spend so long writing something up if I didn't think it was for a good reason. I can see potential in you, and I don't mean to seem rude when I say this... Teachers who are unwilling to try new things and to learn new things are not near as useful as those who realise, life is one big lesson and we don't stop learning simply because we've graduated from university or been working for ten years or whatever. I think you're great, but you could be soooo much better!

edit: I'm aware that the formatting is shitpoor. I tried :( but the editor isn't exactly WYSIWIG if you know what I mean.

it is recommended to input all data as strings and convert them yourself



The only people you'll find who recommend reading input this way tend to do so either because:

  • They don't know how to use `fscanf` properly; these people made a mistake, found out the hard way (probably a bug in production) and only went so far as to conclude that they weren't using `fscanf` correctly (or as they'll put it, `fscanf` is to blame). By this logic if we chop off our legs by abusing a circular saw, the saw is to blame...

  • They went one step further and learnt how to use `fscanf` properly (or at least semi-properly), but use the fact that people find it easy to misuse `fscanf` to support their argument. By this logic, people shouldn't use C because it's too easy to mess up; stick to Python or Java.



Either way, this is appealing from ignorance. I'd rather not use such a citation, partially because it makes me look ignorant, as though I've just read some advice on a forum and blindly followed it without looking deeper into the issues and forming an educated understanding.




I never really have the need for it.



Again, you're not going to find a citation to support that, because it's speculative. By this logic I could say you don't need computers, to support a recommendation that you shouldn't use computers.

Like computers, we don't need many aspects of C; they're purely there for our convenience. It is convenient to be able to convert a sequence of decimal digit characters straight from a stream to an int or what-not, without an intermediate "buffer" (which makes it more optimal, by the way). The "continue;" expression is another example of convenience.




Scanf does not provide a buffer size for input and could cause beginners to buffer overflow.



This is really two statements disguised as one. Those statements need to be addressed separately.

Scanf does not provide a buffer size for input...



According to C11, section 7.21.6.2 paragraph 3...

Each conversion specification is introduced by the character %. After the %, the following appear in sequence:




  • An optional assignment-suppressing character *.



  • An optional decimal integer greater than zero that specifies the maximum field width (in characters).




The emphasis is mine; technically speaking, `fscanf` (and `scanf`, by consequence) gives you an option to provide the size of the array, and then it won't cause an overflow.

I assume you're talking about use of `%s` here, as people who learn about `%[` tend to also learn about maximum field widths along-side. An important point to note is that `%s` serves a subtly different purpose to `fgets`; if you were to use `%511s` to read into an array of 512 char, you would probably find that `fscanf` reads a single whitespace-delimitered token, as opposed to a newline-delimitered token. This forms a unique usecase that `fgets` can't solve on it's own.




... could cause beginners to buffer overflow.



If your argument is that people shouldn't use functions without first fully reading and understanding the manual, you're right. C isn't a very beginner-friendly language, unfortunately. Beginners who aren't willing to do a lot of reading should probably stick to a language that doesn't have undefined behaviour, such as Python or Java. Alternatively, reading the manual prior to using a function serves to be colossally useful, even in languages such as Python and Java; by doing so we can:


  • Avoid common pitfalls caused by ignorant "newbies", because we've avoided ignorance by being proactive about our understanding.

  • Learn about immensely useful nuances of functions that we wouldn't have learnt about otherwise, particularly if we just packed that function into the "too hard" box and left it there as so many people do :(






Scanf also does not clean the input stream. There can be cases where fgets also can't clean the stream in cases where the input provided exceeds the size of the buffer, but that is fairly common sense. I have a habit of clearing the input stream myself each time I use it.



My response to this may surprise you... It's good that you've correctly understood that `fgets` also doesn't solve this problem fully either; you need to resort to a loop, don't you? No...

Well, surprise surprise, you can solve this problem using `fscanf` (and by consequence `scanf`) alone... Observe:

scanf("%*[^\n]");
scanf("%*c");



This also forms a unique usecase for `scanf`. Writing such a `getchar`/`fgetc`-loop is, by contrast, error-proned and tedious. Unfortunately most uses of `getchar`/`fgetc` are as incorrect as most uses of `scanf`/`fscanf`, particularly when used in conjunction with `<ctype.h>` functions (which is rather unfortunate, since `getchar`/`fgetc` are fusional with `<ctype.h>` functions). and the same goes for `fgets`. People tend to think these functions just do whatever is intuitive in their minds, but that which is intuitive is subjective, at least initially.

I hope I've opened your mind, Laura. I spent a long time writing this because I can see that you can be an AWESOME teacher. You just need to keep your mind open, think for yourself and do research to support your beliefs. I know, it can be hard to admit when we're wrong, particularly at first... but it gets easier over time. Trust me, stick in there and step outside of your comfort zone and it'll become second nature to you. :)
0 Eric Hines · January 12, 2016
I appreciate all the help. But since I know practically nothing of C or programming (programming is a lot like my logic class in college, but I wasn't the best at it, so I don't think I'll make programming a career choice), I think I'll need some simpler explanation. Some things on my mind;

1). How do I write my programs so they are based on user input how do I make them interactive? More specifically;
Bucky's tutorials are awesome, but typically he starts out declaring the values of variables outright. for example; 

int main()
{
int tuna='1';

if(isalpha(tuna))
{
printf("%c is a letter", tuna);
}
else
{
    if(isdigit(tuna))      
    {
        printf("%c is a number", tuna);
    }
}

return 0;
}


He set tuna ='1'  in order to demonstrate what the rest of the program does for the sake of the student learning, and I understand this. But, I want to get away from it. I'd like to prompt the user/ make the program interactive. So my question is, how do I leave the variable "open" if you will, so the user can input whatever they choose/ program reads their input/ the program reacts based on that input.

"you do not need to use scanf on your conditions, that doesn't make sense."/ "You are meant to retrieve a string passcode from the user then check all the conditions yourself."

.....it wasn't clear that I was personally to be the one to check the code. I assumed the point of the lesson/challenge was to prompt the user with instructions/ write code to validate they've followed them/ then inform them if they have or haven't followed the directions for a password. 

"you do not need all those headers"  Yes. However, Bucky did recommend we start getting used to them albeit they may not be necessary yet. 

"checking every condition at once isn't such a good idea...."  I agree. I did think about this, but I stayed positive and motivated in an attempt to figure out if I could pull it off. 

 I suppose in that sense scanf is what automatically came to mind. Which leads to my next problem



2). Not quite getting the benefit of fgets over scanf a). in general (aside from what Bucky said about accepting spaces which scanf won't)  and  b). for this particular exercise.


So again, this is where I'm leaning...

"password must be min of x characters. It must have at least 1 uppercase letter, 1 special character, and 1 number. Enter your password now.
User inputs their password.
program checks it
informs user if they're good to go or not (because of x reason why).




Thanks again for the help. Usually my cries for help in forums are passed over, so it's cool seeing the conversation being generated. Also, I love constructive criticism. It helps me grow and learn. And, I have thick skin, so lay it on (professionally, however).  Just be ready for a lot of questions  : D  because I'm having fun and I want to improve. 


Thank you again. 

"the only stupid question is one that is never asked/ the worst they can do is say no"
0 Eric Hines · January 12, 2016
also, is there something special about "i" in variables? I see it all the time
0 Linguist Llama · January 12, 2016
Interesting... Unfortunately it's much harder for me to ask questions that gauge your understanding (and have you respond to them) than it is for you to ask questions about something you don't understand. Would it help if I prompt for questions occasionally? I shouldn't need to, but I'll give it a try. It's probably in your best interest to quote the sections you don't understand when you ask questions about them. I noticed you're pretty good at this.

Another pressing issue is whether an answer to one question tends to raise ten more. If this is the case, you should probably find a new resource to learn from, as there are more questions going unanswered than answered by the guide you're using.

I prefer not to talk about things that are stylistic. If you offend me slightly with your indentation, for example, I probably won't raise it as an issue unless I think it's caused you to perceive functionality incorrectly.

There is nothing special about the variable 'i'; you're right to observe that it's a commonly used identifier for an integer variable. It's mostly a stylistic choice. A questionable one, mind you, as pervasive use of meaningless identifiers can make code difficult to maintain, hence style can affect functionality as I mentioned earlier... Use with care. That's all I have to say on any matters that are mostly stylistic choices.

You can't (and shouldn't) use scanf to read a character into an int. This is a matter of functionality; it's simply not an appropriate type choice. When scanf sees %c, it expects a pointer to character (e.g. char, signed char or unsigned char). You're giving it a pointer to int. That is bad...

As far as scanf is concerned there's a pointer to a single byte, the single byte gets written to but an int occupies more than one byte, and the rest of the bytes don't get assigned a value...

You might notice in six months time your program doesn't function correctly on some platforms, because it uses uninitialised garbage. Hopefully before then you might have noticed compilation warnings hinting to this problem, but unfortunately you probably won't get an error because scanf is a variadic function... Feel free to ask questions about warnings! Try to avoid ignoring them... They're there for a purpose, you know?

Similarly, isupper or isdigit (or any of the other <ctype.h> functions) are only safe to use on unsigned char values and EOF. Again, you won't get a compiler error if you try to use some negative values that aren't EOF, but you might see runtime errors in six months time... Unfortunately it's not yet common for a compiler to perform this kind of analysis and offer warnings, so you probably won't get a warning about this one.

Either change upperletter, specialchar and number to unsigned char or use getchar/fgetc. getchar/fgetc return int, which is common with the types of variables you have defined...

getchar/fgetc return an `unsigned char` value as an int or EOF which is not an unsigned char value... The `<ctype.h>` functions operate on unsigned char value or EOF. Do you see that it's a perfect match? These functions fuse together well, which means you could write something like: int c = tolower(getchar()); and the return value would be converted to uppercase when it's an alphabet character, or left unchanged when it's not... The output for one function is compatible with the input for the other.

So my question is, how do I leave the variable "open" if you will, so the user can input whatever they choose/ program reads their input/ the program reacts based on that input.



This is a great question, and should be given much design thought! I guess it all boils down to:


  • How will your users use the program? You probably haven't put much thought into this, because Bucky teaches from an IDE and doesn't deviate from that... Consider that it's a console program. This means they're console users used to writing things like `cd other_directory`, `ls -R some_filename`, `cc your_c_code.c` and so on... When you know the console intimately and you know how to program using the console, you'll realise that none of these programs use getchar, scanf, fgets or fread for that matter. They almost always resort to using argv instead. It's simpler for the user and for the programmer.

  • What will you be doing with the user input? In this case it's obvious that you read one password, check it and output some message... then you're done. If you needed to read and check a batch of passwords, then you would need getchar, scanf or stdin... but if you're only checking one, it would be preferable to use argv.



I mentioned argv, which you might not have seen. That would be most unfortunate, but I'm the one who brought up the question so I'll cover it briefly with an example... Feel free to ask questions.

#define UPPERCASE_NEED 1
#define NUMBER_NEED 2
#define SPECIALCHAR_NEED 4 /* ... and so on, doubling each time so our flags don't clash in meaning */

unsigned int password_check(char *str) {
   unsigned int uppercase_need = UPPERCASE_NEED,
number_need = NUMBER_NEED,
specialchar_need = SPECIALCHAR_NEED;
   for (size_t x = 0; str[x] != '\0'; x++) {
       unsigned char c = str[x]; /* str[x] needs to be coerced from a char to an unsigned char
                                  * to safely pass to isupper, etc */
       if (isupper(c))      { uppercase_need = 0;   }
       else if (isdigit(c)) { number_need    = 0;   }
       else if (isgraph(c)) { specialchar_need = 0; }
       /* and so on */
   }
   return uppercase_need + number_need + specialchar_need;
}

int main(int argc, char **argv) {
    /* argc tells you how many elements of argv you can access. Assuming argc is greater than or equal to one, argv[0] is almost always your program name; it's fairly useless. Assuming argc is greater than one, argv[1] will be the first argument... so you can write logic based on the first argument like so: */
    if (argc <= 1) {
        puts("usage:  ./app your_password");
        exit(EXIT_FAILURE);
    }
    int password_needs = check_password(argv[1]);
    if (password_needs >= UPPERCASE_NEED) {
        puts("Your password needs an uppercase letter...");
    }
    else if (password_needs >= NUMBER_NEED) {
        /* ... and so on */
   }
}


I think this is what Laura was getting at; you don't need (and shouldn't use) fgets, scanf, getchar or fread for this program.

"you do not need to use scanf on your conditions, that doesn't make sense."/ "You are meant to retrieve a string passcode from the user then check all the conditions yourself."

.....it wasn't clear that I was personally to be the one to check the code. I assumed the point of the lesson/challenge was to prompt the user with instructions/ write code to validate they've followed them/ then inform them if they have or haven't followed the directions for a password.



Of course your program is meant to perform the checking. Note that I've used the term 'string' a number of times, and in fact my code relies upon string processing (hence the '\0' checks). You have a fixed width field that requires the user enter precisely three characters (not including the commas from the format string, which I gather are erroneous). However the exercise speaks about a string. A fixed width field doesn't necessarily contain a string. Do you have any questions?

"you do not need all those headers" Yes. However, Bucky did recommend we start getting used to them albeit they may not be necessary yet.



I also noticed it's quite common for Bucky to post-pone coverage of a topic. I hope he managed to complete the series? If not, that's no big deal, as there are books that have stood the test of time... Any questions about this?

Not quite getting the benefit of fgets over scanf a). in general (aside from what Bucky said about accepting spaces which scanf won't) and b). for this particular exercise.



fgets is admittedly much more difficult to abuse. It begs the question, rather than leaving the question unasked. Nonetheless, if you ask the same questions about scanf you'll find it can solve the same problems (and then some).
0 Eric Hines · January 13, 2016
Thanks again,

 I do agree with you; there IS a lot I don't know and perhaps finding another source for answers may be best (perhaps even a class) because a lot of my questions are too basic. I'd just end up chasing a white rabbit around and would end up annoying you, so perhaps something a bit more structured like a class would be appropriate.

 And as you mentioned also, one answer just leads to more questions. I may just keep doing as much research on my own as possible and if I'm just absolutely stuck I could resort to the forums. Or maybe if I need help with a general idea of how something works I could stop by here. 


......by the way. I think I've found out the answer of how to go about (at least one way) checking each password character to verify the conditions which I set are met. I have seen a lot of other people using for loop with nested ifs. This is what I'm seeing and this is my interpretation of why it works. (my main concern is whether I'm understanding the a++ rule......

a=0;
for(password[a]; a<=20 ; a++ )                                 
{                                                        
if(isupper(password[a]))         
{                                                    
upperflag = 1; 

.

.

.

.etc                            
                                                     
    it's saying; begin checking with the first character of the password, end with the last character of the password, after each circuit of the loop, increment by one to check the next character in line. In other words, am I right that the for loop can only check one character at a time and it must see if it meets one of the if statements? And if it does, it is assigned a value of 1 which when tested later on and a

 "there are books that have stood the test of time"

 are there any you recommend for someone like me who is just beginning?
0 Eric Hines · January 13, 2016
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <string.h>

int main()
{
char password[3];
int i=0;
int upper=0;
int num=0;
int dollarsign=0;

printf("Enter your password. (must be 3 characters, one uppercase, one lowercase, one number)");
scanf("%s",password);


for(i=0;i<=3;i++)
{
    if(isupper(password))
    {
        upper=1;
    }
    if(isdigit(password))
    {
        num=1;
    }
    if(isdigit(password=='$'))
    {
        dollarsign=1;
    }
}
if(upper==1 && num ==1 && dollarsign ==1)
{
    printf("Thank you for entering your password. \n");
}
else
{
    printf("Your password does not meet the minimum requirements. \n");
}
return 0;
}


I thought I wrote it correctly, but I'm just getting the same thing; your password doesn't meet the min requirements. The password I entered was A1$
0 Eric Hines · January 13, 2016
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <string.h>

int main()
{
int a,b,c;
int i;
char password[5];

printf("enter password. must contain upper letter, number and, $ sign");
scanf(" %s", password);

for(i=0; i<=5; i++)
{
    if(isupper(password))
    {
        a=1;
    }
    if(isdigit(password))
    {
        b=1;
    }
    if(isdigit(password))
    {
        c=1;
    }
}
if(a==1&&b==1&&c==1)
{
    printf("thank you");
}
else
{
    printf("try again");
}
return 0;
}



It's working now.... how frustrating
  • 1
  • 2

C

107,229 followers
About

One of the most popular languages of all time.

Links
Moderators
Bucky Roberts Administrator