I'm new to coding in general, and very new to C.
I'm trying to write a program in c that asks the user for input and based on that users input, prints specific text to a .txt file. Please have a look at my code and let me know what I'm doing wrong.
Please have a look at my code below. Thanks very much for your assistance.
Matt
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
FILE * fptr;
int main(){
char name[32];
char partNum[32];
char desc[250];
char ans1;
char ans2;
char sn[50];
char ansRep;
fptr = fopen("C:\\Users\\mgreene\\Documents\\EmailTemplates\\QCIssue.txt", "w");
if (fptr ==0)
{
printf("Error--file could not be opened\n");
exit(1);
}
printf("What is the customer's first name?\n");
scanf("%s", name);
printf("With which Part number is the user experiencing difficulties?\n");
printf("Enter Part Number:");
scanf("%s", partNum);
printf("Please enter a brief description of the problem\n");
scanf("\n%s", desc);
printf("Does the product have a serial number?\n");
scanf("%c", &ans1);
if (!strcmp(&ans1, "y")== 0)
{
printf ("Do you know the serial number?\n");
scanf("\n%c", &ans2);
if (!strcmp(&ans2, "y")==0)
{
printf ("Please enter the SN:\n");
scanf("\n%s", sn);
fprintf(fptr, "\nHi %s,\n\nI hope this message finds you well.\n\nI write to you today as the Quality Manager at Blank. I received a report that you're experiencing difficulties with part: %s, %s; specifically, the report indicates that %s. Is that an accurate description of the problem? Firstly please accept my apologies on behalf of Blank for the difficulties you're experiencing with this part. As an ISO9001:2008 compliant organization, Blank takes all issues related to customer satisfaction very seriously. It is our intention to resolve this matter as soon as is possible.\n\n", name, partNum, sn, desc);
}
else
{
fprintf(fptr, "\nHi %s,\n\nI hope this message finds you well.\n\nI write to you today as the Quality Manager at Blank. I received a report that you're experiencing difficulties with part: %s; specifically, the report indicates that %s. Is that an accurate description of the problem? Firstly please accept my apologies on behalf of Blank for the difficulties you're experiencing with this part. As an ISO9001:2008 compliant organization, Blank takes all issues related to customer satisfaction very seriously. It is our intention to resolve this matter as soon as is possible.\n\nBefore I can begin an investigation into this problem, I'll need the serial number from that unit. Can you please forward me the serial number as soon as you're able? Once I have that, I can begin the investigation on our end. Thanks.\n\n", name, partNum, desc);
}
}
else if (strcmp(&ans2, "y")==0)
{
printf("Will Blank be sending the customer a replacement? Please enter y or n\n");
scanf("\n%c", &ansRep);
if (!strcmp(&ansRep, "y")==0)
{
fprintf(fptr, "Blank can send you a replacement product as soon as is possible. In order to ensure that the replacements are shipped to the correct address, will you please confirm you shipping address via email? Thanks.\n\n");
fprintf(fptr, "Thank you for your assistance in resolving this matter. Please let us know if you have any additional questions, comments, or concerns about this specific issue or any issues related to products distributed by Blank.\n\n");
fprintf(fptr, "Have a great day!");
}
else
{
fprintf(fptr, "Thank you for your assistance in resolving this matter. Please let us know if you have any additional questions, comments, or concerns about this specific issue or any issues related to products distributed by Blank.\n\nHave a great day!");
}
}
fclose (fptr);
return (0);
}
if (!strcmp(&ans1, "y")== 0) is wrong, which may be modified to if(ans1 != 'y'). You should do similar modifications to if (!strcmp(&ans2, "y")==0), if (strcmp(&ans2, "y")==0), if (!strcmp(&ansRep, "y")==0) in your code.
And for scanf("%c", &ans1);, you may rewrite it as follows:
scanf(" %c", &ans1);
Removing \n in scanf("\n%s" [...] and adding a space before %c in scanf("%c" and scanf("\n%c" (removing \n) may help.
you could create a simple function to catch strays from scanf
void clear_input_buffer(void)
{
char buffer[100];
fgets(buffer, 100, stdin);
}
then include it after scanf
scanf("%s", name);
clear_input_buffer();
First things first: ans1 and ans2 are chars; you are doing strcmp(&ans2, "y") - here, "y" is a string (NULL terminated array of characters) because it is in double quotes. Before anything else, you should replace all your comparisons of those variables with something like
if(ans1 == 'y')
Also, when you read a character you should add a space in front of the %c just in case there is some whitespace or a stray new line from previous input - scanf(" %c", &ans1);
Short answer: I think your strings contain garbage!
Strings in C must be terminated by a Null byte ('\0'). When you create a char array on the stack (or heap) the contents of that array may be filled with whatever junk your memory happened to have at the time. Try running this small program to see what I mean. One might predict this program will print 3 blank lines, but it should instead print 3 lines of random garbage (every time you run this program the garbage should be different).
#include <stdio.h>
int main(){
char name[32];
char partNum[32];
char desc[250];
printf("%s\n", name);
printf("%s\n", partNum);
printf("%s\n", desc);
}
There are a few things you could improve in the program you've posted, but to specifically address your string containing garbage, here is a simple solution:
Replace lines like char name[32]; to char *name = calloc(32, sizeof char); and be sure to free these memory allocations at the end of your program like this free(name).
calloc sets all the memory it allocates to NULL so your strings will no longer contain garbage.
If you are experiencing garbage in your output file, that should do the trick.
Also note
You are using fixed size char arrays. What happens if a user decides to type something in thats longer than you expected? The input will bleed into other parts of memory and can affect the values of other variables.
Related
I'm trying to create a small book library in C so I wrote a function that insert a book by it's name, id and quantity. For some reason the program is ruining fine and the function seems to be working but the external file remain unchanged (no new data is added). I checked the path of the file but still the problem persist. how can I fix this?
here's the function and the structure:
struct library{
int id;
int qty;
char name[50];
};
void InsertBook()
{
struct library b;
FILE *books;
if((books=fopen("C:\\mybooks.txt","a+")==NULL))
{
printf("file not found\n");
}
else
{
printf("You will need to enter a name, ID, and quantity of the book.\n");
printf("please enter book name:");
fflush(stdin);
fgets(b.name,SIZE,stdin);
fputs(b.name,books);
printf("please enter book ID:");
scanf("%d",&b.id);
printf("please enter book quantity:");
scanf("%d",&b.qty);
fprintf(books,"%d %s %d\n",b.name,b.id,b.qty);
fclose(books);
}
}```
The only mistake you've done is:
fprintf(books, "%d %s %d\n", b.name, b.id, b.qty);
Just do:
fprintf(books, "%s %d %d\n", b.name, b.id, b.qty); // %s before %d
And everything's OK.
Side Tips
fputs() not required here.
books = fopen("C:\\mybooks.txt", "a+") == NULL expression won't work for most compilers because the assignment makes pointer from integer without a cast.
Instead you can assign it before comparing it to the null expression as shown:
books = fopen("C:\\mybooks.txt", "a+");
if (books == NULL) { ... }```
Important note: Ensure you have writing permission in that destination.
The program does work (but see errors below), however you are writing to the root folder of Drive C. The output file is visible from a console, but not from Windows file explorer. Normally, you should not use the root folder of C drive.
There is a parenthesis in the wrong place here
if((books=fopen("mybooks.txt","a+")==NULL))
which should be
if((books = fopen("mybooks.txt","a+")) == NULL)
There are wrong format specifications wrong in fprintf here, as complained by the compiler
fprintf(books,"%d %s %d\n",b.name,b.id,b.qty);
should be
fprintf(books, "%s %d %d\n", b.name, b.id, b.qty);
You unnecessarily duplicate writing the book title to file a few lines above with
fputs(b.name,books);
You use an incorrect buffer size for fgets here
fgets(b.name,SIZE,stdin);
which should be
fgets(b.name, sizeof b.name, stdin);
Also, you must check the return values particularly from user input functions such as fgets and scanf.
Additionally there are faults waiting to happen with the input of the book title here
fgets(b.name, sizeof b.name, stdin);
It retains the newline which will be part of the data. Please see Removing trailing newline character from fgets() input
When you make a loop to input more books, it will catch the newline left in the buffer by scanf. Please see Please see scanf() leaves the newline char in the buffer.
So I suggest you replace that line with
if(scanf("%49[^\n]", b.name) != 1)
exit(1);
It is best not to mix the input methods: stick to one.
What is going on here?
The code goes like:
#include<stdio.h>
#include<string.h>
int main()
{
char name[15];
char name_[15];
char answ[1];
printf("What's your name?\n");
scanf("%s", name);
strcpy(name_, name);
printf("Yes / No: ");
scanf("%s", answ);
printf("Hello! %s\n", name_);
printf("You said: %s\n", answ);
return 0;
}
With input "name" and "yes" the expected output is that it says:
Hello! name
You said: yes
Instead I get:
Hello! es
You said: yes
I also tried adding spaces before %s with no results.
So what exactly am I missing here?
answ can contain only 1 character. So currently, the extra character "es" + '\0' gets written into the memory assigned to name_.
So, "es" gets printed.
You've only allocated space for a one-character yes/no answer, but are writing more characters into it.
This results in undefined behaviour.
You need to allocate more space for answ, not forgetting about the NUL terminator.
You have created a classic exploitable buffer overrun but in your code. This is why most modern compilers would advise you to swap sscanf to sscanf_s or similar. As other people have pointed out, you overwrite the next variable on the stack.
I wanted to provide this answer to basically say: never ever use sscanf or any of the obsolete, insecure C functions. Even if this is probably just a toy example, get the practice in to write modern C code. You’ll benefit from this in the long run.
I'm trying to learn to how to code myself so there're alot of thing that I didn't know.
What I'm trying to do is tell the compiler to re execute the code at specific point when if statement is true
#include <stdio.h>
int main() {
char name[25];
char first_name[25];
char last_name[25];
printf("Please put in your first name here: ");
scanf("%s", first_name);
printf("Please put in your last name here: ");
scanf("%s", last_name);
/*If this statement below is true, I want the compiler to execute the code above */
if (strlen(first_name) > 5 || strlen(last_name) >5)
{
printf("Last name or First name is too long!\n");
}
printf("Welcome %s %s!", first_name, last_name);
return 0;
If you're designing a console application, you might want to consider taking user input from the command line arguments. i.e.
#include <stdio.h>
int main(int argc, char **argv) {
if (argc <= 2) {
fputs("insufficient arguments", stderr);
exit(EXIT_FAILURE);
}
if (strlen(argv[1]) > 5) {
fputs("first name too long", stderr);
exit(EXIT_FAILURE);
}
if (strlen(argv[2]) > 5) {
fputs("last name too long", stderr);
exit(EXIT_FAILURE);
}
printf("Welcome %s %s!", argv[1], argv[2]);
}
This form of input feels much more natural; you don't have to worry about array sizes or object lifetime... not to mention the end user gets to enjoy autocomplete and your software will blend into their ecosystem better, too. I'm sure they'd rather press the "up" arrow to fix a typo in their command than re-enter the entire entry... if you plan on having users, that is... the console doesn't have many, and those who do enjoy highly scriptable software; they'll write their own alternative if you do a poor job.
If you're not convinced, and you still want to use scanf, you need to:
check the return value
bring the input stream to a sanitary state after invalid input
(usually it's best to) read and handle errors for one field at a time
Check the return value
The return value for scanf is the number of objects successfully matched and assigned to, or EOF in the case of some read failure. If the input is not of the format described by your format string, you might get some kind of match failure, and scanf returns. This is particularly troublesome for numeric directives and %[scanset] directives, both of which commonly fail when users make some mistake, which is why it's necessary to bring the input stream to a sanitary state after invalid input.
Bring the input stream to a sanitary state after invalid input
This is done by reading and discarding characters from the stream until we reach some state where the input might successfully match the format string. We could discard one character and try again, for example, or keep discarding until a whitespace is encountered (e.g. scanf("%*[^ ]");). I tend to discard until a newline is encountered (see example below) to mimic the functionality of using fgets+sscanf (this is another option, btw, but you might still need to bring the stream to some sanitary state if you don't get a full line).
Matching multiple fields with one call increases the complexity of error handling. For each field after the first, we must perform the sanitation step and resume matching, which makes our code more bulky and repetitive, something we seek to eliminate. Nonetheless, here's a brief example of trying to read two fields with one call:
void example_1() {
int x, y;
switch (scanf("%d %d", &x, &y)) {
case EOF: exit(0);
case 0: scanf("%*[^\n]"); example();
case 1: for (;;) { // x assigned, y uninitialised
scanf("%*[ ]");
switch (scanf("%d", &y)) {
case EOF: exit(0);
case 0: continue;
}
}
}
}
The more fields, the more nested switches. It's easy to see that this is a terrible way to read two fields, let alone three. Perhaps something like this would be appropriate, per field:
do {
fputs("Please enter your first name: ", stdout);
if (scanf("%24s", first_name) != 1) {
fputs("(most probably) I/O error", stderr);
exit(EXIT_FAILURE);
}
} while (strlen(first_name) > 5 && (fputs("First name too long", stderr),
scanf("%*[^\n]") == 0));
This really only skims the surface of a topic that has dragons in a language that has dragons, which should be a red flag in a red flag to you. If you're not reading a book, or you don't have Kernighan & the late Ritchie themselves (or some professor roughly as reputable) as your professors, then you should get something like that... scanf has too many subtle nuances to just guess your way around it.
Well you can use term Loop instead of re-execute..
Here, in your problem you can use looping statements like do-while loop.
Or else we have other method using jump statements.(goto in this case).
Below is your code.
#include <stdio.h>
#include <string.h>
int main() {
char name[25];
char first_name[25];
char last_name[25];
input:
printf("Please put in your first name here: ");
scanf("%24s", first_name);
printf("Please put in your last name here: ");
scanf("%24s", last_name);
/*here %24s is used to specify the size of the buffer i.e 24
So if you give a string more than 24 characters then %24s takes only
24 characters then rest is ignored */
/*If this statement below is true, I want the compiler to execute the code above */
if (strlen(first_name) > 5 || strlen(last_name) >5)
{
printf("Last name or First name is too long!\n");
goto input;
}
printf("Welcome %s %s!", first_name, last_name);
return 0;
}
But usage of goto statements are not recommended because they make logic complex
Now, you try using do-while loop.
My goal with this program is to incorporate the users inputs into a sort of interactive/randomized story but I'm not sure how I'm supposed to get the inputs from the users to fit between *ptrDescription, *ptrBeginning, *ptrMiddle, and *ptrEnd. Any help would be much, much appreciated!
#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#include <string.h>
#include <ctype.h>
int main(void){
int i;
char name[20];
char color[20];
int age;
char sentence[1];
//array of pointers to char arrays
char *ptrDescription[]={"the painfully handsome","the one and only","who seemed much older than"};
char *ptrBeginning[]={"was blissfully ignoring","could clearly see","had no idea"};
char *ptrMiddle[]={"the huge truck","the falling meteor","the bucket of milk","the mailman","the most powerful wizard"};
char *ptrEnd[]={"that was barreling toward them.","on the horizon."};
srand(time(NULL));
printf("Enter your first name: ");
scanf("%s", &name);
printf("\nEnter your age: ");
scanf("%d", &age);
printf("\nEnter your favorite color: ");
scanf("%s", &color);
for (i = 0; i < 1; i++)
{
//strcpy(sentence,ptrDescription[rand()%3]);
//strcat(sentence," ");
//strcat(sentence,ptrBeginning[rand()%3]);
//strcat(sentence," ");
//strcat(sentence,ptrMiddle[rand()%5]);
//strcat(sentence," ");
//strcat(sentence,ptrEnd[rand()%2]);
//strcat(sentence,".");
//sentence[0]=toupper(sentence[0]);
puts(sentence);
}
getch();
return 0;
}
EDIT:
I've edited a section of my code so that directly following for (i = 0; i < 1; i++) it now looks like this:
snprintf(sentence, sizeof sentence,"%s, %s %d year old, %s %s %s %s", name, ptrDescription[rand()%3], age,ptrBeginning[rand()%3], ptrMiddle[rand()%5], ptrEnd[rand()%2]);
There are tons of strange characters after the sentence in the output, like Japanese characters and stuff. I'm not sure why they're there, though. This is what it looks like exactly:
"Enter your first name: Justin
Enter your age: 20
Justin, the arrogant 20 year old, was purposefully ignoring the most powerful wizard that was barreling toward them. 汽$0HβHζ(テフフフフフフフフフフフフフH・(DキHH広$0陏&・汽$0タHζ(テフフフフフフフフフフフフフフフH WH・ H櫛H・t9HνHテ<"
Anyone know how I can get rid of them?
If you already have a name and an age, it's just a matter of inserting them into the correct place in sentence, right? So strcat(sentence, name) would work for name. age is a little trickier since you have to format the number first, and strcat won't do it for you. One solution would be to use sprintf(buf, "%d", age), and then concatenate buf (which is a scratch char array you would have to declare).
Any time you work with strings in C, you have to be concerned about having enough space in the target buffer. Your program can run out of space during both input and output. For the output, I would get rid of sentence altogether; since you just end up writing to stdout I would printf("%s", [part]) each part as you go along. For reading, scanf supports adding a length argument to the format string.
If you use one of the *printf functions, there are 2 things you must be careful about:
The arguments you pass are correct for the format string you use
Your buffer ends up null-terminated
Your current problem is with #1 - your format string promises 7 arguments to follow, but you only supply 6. snprintf grabs a "random" 7th value from the stack, interprets it as a char pointer, and copies whatever it finds there to sentence. You could see similar problems if your format string promised a char pointer but you placed an int in a given position. In this case the format string is a constant, so a smart compiler can validate that your format string matches the subsequent parameters. You'll want to get into the habit of taking compiler warnings seriously and not ignoring them.
The second point could be an issue if your sentence ended up bigger than your sentence buffer. If there is no room for a null-terminator, one won't be applied. You can check the return value of snprintf, or you can defensively always write a 0 to the last array position.
I think I've tried anything (flushing stdin, scanf to consume newline etc.), but nothing works as I had hoped. For some reason a 3rd scanf modifies a variable from 2nd scanf in the following code:
#include <stdio.h>
int main()
{
char first_name[16], last_name[21];
char filename[11];
FILE *opening;
printf("The program saves your first and last name into a file.\n");
printf("Enter your first name:");
scanf("%s", first_name);
getchar();
printf("Enter your last name:");
scanf(" %s", last_name);
getchar();
printf("File where you want to save your name:");
scanf(" %s", filename);
opening = fopen(filename, "wb");
fprintf(opening, "%s %s", first_name, last_name);
printf("\nSuccessfully saved the data!");
fclose(opening);
return 0;
}
The output:
The program saves your first and last name into a file.
Enter your first name: John
Enter your last name: Doe
File where you want to save your name: filename.txt
Successfully saved the data!
All fine and dandy except that the contents of filename.txt is this:
John t
I'm guessing that the 't' character comes from 'txt' somehow, but I've just started learning C and I don't know how to fix this piece of code to work. Could you gurus help me please?
Your filename buffer is too small.
You write filename.txt, which is 12 characters, plus the zero to finish it, makes 13. You only allocate 11. Try like this:
char filename[20];
and it should work.
Be careful though with using scanf, it can lead to very nasty problems, as you are encountering right now. It is good in experimenting and learning C, as it shows you how important correct memory handling is. For any real project you should consider using different functions or frameworks.
Using scanf() on strings is dangerous, as it may read in more data into the buffer than the buffer provides memory.
If scanning in strings one shall always tell scanf() how much characters to read by adding this number to the format passed to scanf():
char file_name[11];
...
scanf("%10s", file_name); /* As file_name provides memor for 11 characters, read a
maximum of 10 characters into file_name leaving 1
character room for the necessary `0-`terminator indicating
the end of the "string". */
Also your code misses error checking on the fopen system call.
Better do something like this:
opening = fopen(filename, "wb");
if (NULL == opening)
{
perror("fopen() failed");
exit(EXIT_FAILURE);
}
If you are entering filename.txt as your file name, then you are overrunning your buffer for filename. That is undefined behaviour and is the cause of the strange results.
To fix, make char filename[11]; larger, remembering to allow 1 extra character for the NULL terminator. In your very specific case, that would be char filename[14]; allowing for the errant space before %s in your scanf call.
Otherwise, all looks fine.