This is literally the first thing I've ever written in C, so please feel free to point out all it's flaws. :) My issue, however is this: if I write the program the way I feel is cleanest, I get a broken program:
#include <sys/queue.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* Removed prototypes and non related code for brevity */
int
main()
{
char *cmd = NULL;
unsigned int acct = 0;
int amount = 0;
int done = 0;
while (done==0) {
scanf ("%s %u %i", cmd, &acct, &amount);
if (strcmp (cmd, "exit") == 0)
done = 1;
else if ((strcmp (cmd, "dep") == 0) || (strcmp (cmd, "deb") == 0))
debit (acct, amount);
else if ((strcmp (cmd, "wd") == 0) || (strcmp (cmd, "cred") == 0))
credit (acct, amount);
else if (strcmp (cmd, "fee") == 0)
service_fee(acct, amount);
else
printf("Invalid input!\n");
}
return(0);
}
void
credit(unsigned int acct, int amount)
{
}
void
debit(unsigned int acct, int amount)
{
}
void
service_fee(unsigned int acct, int amount)
{
}
As it stands, the above generates no errors at compile, but gives me a segfault when ran. I can fix this by changing the program to pass cmd by reference when calling scanf and strcmp. The segfault goes away and is replaced by warnings for each use of strcmp at compile time. Despite the warnings, the affected code works.
warning: passing arg 1 of 'strcmp' from incompatible pointer type
As an added bonus, modifying the scanf and strcmp calls allows the program to progress far enough to execute return(0), at which point the thing crashes with an Abort trap. If I swap out return(0) for exit(0) then everything works as expected.
This leaves me with two questions: why was the original program wrong? How can I fix it better than I have?
The bit about needing to use exit instead of return has me especially baffled.
This is happening because of the scanf statement.
Look how cmd is pointing to NULL. When scanf is run, it writes to the address of cmd, which is NULL, thus generating a segfault.
The solution is to create a buffer for cmd, such as:
char cmd[20];
Now, your buffer can hold 20 characters. However, you now need to worry about buffer overflows if a user enters more than 20 characters.
Welcome to C.
EDIT: Also, note that your credit, debit, and service fee functions won't work as expected as you have them written. This is because the parameters are passed by value, not by reference. This means that after the method returns, any changes will be discarded. If you want them to modify the arguments you give, try changing the methods to:
void credit(unsigned int *acct, int *amount)
And then call them like:
credit(&acct, &amt);
Doing that will pass the parameters by reference, meaning that any changes you make inside the credit function will affect the parameters, even after the function returns.
You aren't allocating memory for cmd, so it's NULL.
Try declaring it with some space:
char cmd[1000];
As others have pointed out, you haven't allocated anything for scanf to read into. But you should also test the return value of scanf:
if ( scanf ("%s %u %i", cmd, &acct, &amount) != 3 ) {
// do some error handling
}
The scanf function returns the number of succesful conversions, so if someone types in XXXX when you expect an integer you want to be able to detect and deal with it. But frankly, user interface code that uses scanf() is never really going to be proof against this sort of thing. scanf() was actually intended for reading formatted files, not random input from humans.
This :
char *cmd = NULL;
Should be:
char cmd[100];
Please note:
You should ensure that the string the user will input in cmd has length less than 100 or n
cmd is initialised to a null pointer which never points at any memory. scanf doesn't check that cmd is valid before trying to write to what cmd points to.
A preliminary solution instead creates some space for cmd to point to:
char cmd[30]; /* DANGEROUS! */
but this is a very dangerous move because you may still get segfaults if the input is longer than expected and scanf tries to write to cmd[30] and beyond.
For this reason scanf is considered unsafe and should not be used in production code. Safer alternatives include using fgets to read a line of input and sscanf to process it.
Sadly, C I/O is very difficult to get right without introducing the possibility of buffer overflows into your program. You always need to be thinking about how much memory you have available and whether it will be enough to store the longest possible input you could receive. You also need to check the return values of most I/O functions for errors.
In your example, scanf() is being passed a null pointer.
char *cmd = NULL;
scanf() won't allocate space for the string - you'll need to allocate somewhere for the string to go.
char cmd[80];
...
scanf ("%s",cmd);
Your getting a segmentation fault because scanf() is attempting to write its output to unallocated space.
Others have pointed out the error in your program, but for a better understanding of pointers, since you are just starting to learn C, look at this question at SO.
Your basic problem is that you haven't allocated memory for your string. In C, you are responsible for all memory management. If you declare variables on the stack, this is easy. With pointers, it's a little more difficult. Since you have the line char* str = NULL, when you attempt to scanf into it, you write bytes to NULL, which is illegal. What the %s specifier does is write into what str points to; it can't change str, as parameters are passed by value. This is why you have to pass &acct instead of just acct.
So how do you fix it? You need to provide memory where the read-in string can live. Something like char str[5] = "". This makes str a five-element character array, big enough to hold "exit" and its terminating zero byte. (Arrays decay into pointers at the slightest provocation, so we're fine on that front.) However, this is dangerous. If the user enters the string malicious, you're going to write "malic" into str and the bytes for "icious\0" into whatever comes after that at memory. This is a buffer overflow, and is a classic bug. The simplest way to fix it here is to require the user to enter a command of at most N letters, where N is the longest command you have; in this case, N = 4. Then you can tell scanf to read in at most four characters: scanf("%4s %u %i", cmd, &acct, &amt). The %4s says "read in at most four characters", so you can't screw up other memory. However, note that if the user enters malformed 3 4, you won't be able to find the 3 and the 4, since you'll be looking at ormed.
The reason you could do scanf("%s %u %i", &cmd, &acct, &amount) is that C is not type-safe. When you gave it &cmd, you gave it a char**; however, it was happy to treat it as a char*. Thus, it wrote bytes over cmd, so if you passed in the string exit, cmd might (if it were four bytes wide and had the appropriate endianness) be equal to 0x65786974 (0x65 = e, 0x78 = x, 0x69 = i, 0x74 = t). And then the zero byte, or any other bytes you passed in, you would start to write over random memory. If you change it at strcmp too, however, it will also treat the value of str as a string, and everything will be consistent. As for why return 0; fails but exit(0) works, I'm not sure, but I have a guess: you may have been writing over the return address of main. That's stored on the stack too, and if it happens to come after cmd in the stack layout, then you might be zeroing it or scribbling on it. Now, exit must do its cleanup manually, jumping to the right locations, etc. However, if (as I think is the case, although I'm not sure) main behaves like any other function, its return jumps to the space on the stack stored as the return address (which is probably a cleanup routine of some sort). However, since you've scribbled over that, you get an abort.
Now, there are a couple of other small improvements you could make. First, since you're treating done as a boolean, you ought to loop while (!done) { ... }. Second, the current setup requires you to write exit 1 1 to exit the program, even though the 1 1 bit shouldn't be necessary. Third, you should check to see if you have successfully read all three arguments, so you don't get errors/inconsistencies; for instance, if you don't fix this, then the input
deb 1 2
deb 3 a
Calls debit(1,2) and debit(3,2), while still leaving the a in the input to trip you up. Finally, you should exit cleanly on EOF, rather than looping forever doing the last thing you did. If we put this together, we get the following code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void credit(unsigned int acct, int amount);
void debit(unsigned int acct, int amount);
void service_fee(unsigned int acct, int amount);
int main() {
char cmd[5] = "";
unsigned int acct = 0;
int amount = 0;
int done = 0;
while (!done) {
if (feof(stdin)) {
done = 1;
} else {
if (scanf("%4s", cmd, &acct) != 1) {
fprintf(stderr, "Could not read the command!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if (strcmp(cmd, "exit") == 0) {
done = 1;
} else {
if (scanf(" %u %i", &acct, &amount) != 2) {
fprintf(stderr, "Could not read the arguments!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if ((strcmp(cmd, "dep") == 0) || (strcmp(cmd, "deb") == 0))
debit(acct, amount);
else if ((strcmp(cmd, "wd") == 0) || (strcmp(cmd, "cred") == 0))
credit(acct, amount);
else if (strcmp(cmd, "fee") == 0)
service_fee(acct, amount);
else
fprintf(stderr, "Invalid input!\n");
}
}
/* Cleanup code ... */
}
return 0;
}
/* Dummy function bodies */
void credit(unsigned int acct, int amount) {
printf("credit(%u, %d)\n", acct, amount);
}
void debit(unsigned int acct, int amount) {
printf("debit(%u, %d)\n", acct, amount);
}
void service_fee(unsigned int acct, int amount) {
printf("service_fee(%u, %d)\n", acct, amount);
}
Note that if there is no "cleanup code", you can replace all your uses of done with break and remove the declaration of done, giving the nicer loop
while (1) {
if (feof(stdin)) break;
if (scanf("%4s", cmd, &acct) != 1) {
fprintf(stderr, "Could not read the command!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if (strcmp(cmd, "exit") == 0) break;
if (scanf(" %u %i", &acct, &amount) != 2) {
fprintf(stderr, "Could not read the arguments!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if ((strcmp(cmd, "dep") == 0) || (strcmp(cmd, "deb") == 0))
debit(acct, amount);
else if ((strcmp(cmd, "wd") == 0) || (strcmp(cmd, "cred") == 0))
credit(acct, amount);
else if (strcmp(cmd, "fee") == 0)
service_fee(acct, amount);
else
fprintf(stderr, "Invalid input!\n");
}
In order to fully understand what is going on here you need to understand some basics about C pointers. I suggest you take a look here if you are really that new to C:
http://www.cprogramming.com/tutorial.html#ctutorial
The most common cause of segfaults are detailed here:
http://www.cprogramming.com/debugging/segfaults.html
Related
I am trying to read from a file a non specific number of integers in pairs. I also want to skip lines that start with #. My problem is that nothing gets printed. When I tried to printf the value returned by fgets, it printed null. I would really appreciate a little help since I am not very experienced with C and I would be very grateful if you did not focus on feof since I have already read why is feof bad.
The file looks like this:
#This must
#be
#skipped
1233 14432
4943928 944949
11233 345432
And the code is:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct{
int start;
int end;
}path;
int main()
{
path* array;
array=malloc(5*sizeof(path));
if(array==NULL){
printf("Error allocating memory\n");
abort();
}
FILE* fd=fopen("File.txt","r");
if(fd==NULL){
printf("Error opening file\n");
abort();
}
char buff[200];
int counter=0;
if(fopen==NULL){
printf("Error opening file\n");
abort();
}
char c;
while(!feof(fd)||counter==6){
fgets(buff,200,fd);
c=buff[0];
if(strcmp(buff[0],"#")){
continue;
}
sscanf(&buff,"%d %d",array[counter].start,array[counter].end);
printf("%d\t%d\n",array[counter].start,array[counter].end);
counter++;
}
fclose(fd);
free(array);
return 0;
}
First, answering the title of your question: fgets() returns NULL at end of file and not when a file is empty.
Anyway, your test in the while loop is incorrect:
feof() only gives a true result when you have already tried read and you have already hit the end of file with an unsuccessful read. As read tries to give you as many bytes as it can... or none at all if end of file, the only way to get end of file condition is after you failed to read something. It is far better to check for fgets() result, as it returns NULL on being unable to read anything now. (and not in the last read) so
while(fgets(buff, sizeof buff, fd) != NULL)
or just
while(fgets(buff, sizeof buff, fd))
would be far better. Also, see how I use the sizeof operator to use the size of the used buffer, instead of repeating (and being error prone) the actual number of bytes at two places. If you decide to change the size of the buffer, you'll need also to change the actual number of bytes to read in the fgets() call, making the possibility of forgetting one of them an opportunity to run into trouble.
you order to stay in the loop only while !feof() OR when counter == 6 (first, this will make the control to enter the loop when counter is equal to 6, despite you have reached EOF or not, this cannot be correct) Think that you only get out of the loop when both conditions are false (this means feof() returns true and also counter != 6), you had better to write:
while(fgets(buff, sizeof buff, fd) && counter < max_number_of_iterations)
The test
if(strcmp(buff[0],"#"))
is also incorrect, as buff[0] is a character (indeed, it is the first character read in the buffer, and "#" is a string literal (not a character) Probably you got at least a warning from the compiler, from which you say no word. You had better to test both characters for equality, as in
if (buff[0] == '#') /* this time '#' is a character literal, not a string literal */
in the line
if (fopen == NULL)
fopen by itself is a pointer to the library function fopen(3) which is not what you want (fopen is always != NULL) but
if (fd == NULL){
(which you do before, so you had better to eliminate al this code)
you define a char c;, then initialize it to the first char of buff, and then you don't use it at all. This has no impact in your code but it's bad style and confounds maintainers in the future.
in the line sscanf(&buff, "%d %d", .... you don't need to pass &buff, while buff is already a char pointer. It is better to pass it buff.n But instead, you need to pass pointers to the variables you are reading so you need it corrected into:
sscanf(buff, "%d%d", &array[counter].start, &array[counter].end);
not doing so will make an Undefined Behaviour that will be difficult to pursue, as the use of uninitialised variables (and more on, pointers to variables) will make the code probably work at first, but fail when it has got to production for a while... this is a very serious error.
Your code, with all these errors corrected, should look like:
pru.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N (5) /* I have defined this constant so you can
* change its value without having to go all
* the code for occurrences of it and
* changing those */
typedef struct{
int start;
int end;
} path;
int main()
{
path* array = malloc(N*sizeof(path)); /* better declare and init */
if(array==NULL){
printf("Error allocating memory\n");
abort(); /* have you tried exit(EXIT_FAILURE); ?? */
}
FILE* fd=fopen("File.txt","r");
if(fd==NULL){
printf("Error opening file\n");
abort();
}
char buff[200];
int counter=0;
while(fgets(buff, sizeof buff, fd) && counter < N){
if(buff[0] == '#'){
continue;
}
sscanf(buff, "%d %d", &array[counter].start, &array[counter].end);
printf("%d\t%d\n", array[counter].start, array[counter].end);
counter++;
}
fclose(fd);
free(array);
return 0;
}
Running the code shows:
$ pru
1233 14432
4943928 944949
11233 345432
with the File.txt you posted.
Finally, a hint:
Despite of your interest in knowing only the reasons of your loop falling, and not why feof() is of no use here (and many other things you just don't ask for and that are wrong in your code), if that is actually the case, you had better to post an example that only shows the failing behaviour as recommended by the page How to create a Minimal, Complete, and Verifiable example you should have read and which I recommend you to do.
You should not check feof() in the while condition. See Why is “while ( !feof (file) )” always wrong?.
The loop should be:
while (fcounter < 5 && fgets(buff, 200, fd))
I have the following piece of code. What the program does is ask the user for 2 strings. For the first one i tried out using a memmory allocated string with malloc() and used getc() in order to het the input from the user. For the second string i used an array of characters with specified size and scanf(). The issue i am having is that scanf gets the value that exceeded from the getc() used some lines of code before. How can i stop this behaviour? Also is enaString[ctr] = '\0'; and diaxwristiki[LINESIZE-1] = '\0' considered undefined behaviour? Or this is the right way of adding the null terminating character into your string?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[])
{
int LINESIZE = 15; //maximum length of array of characters
int ctr = 0;
char * enaString = NULL, xaraktiras, diaxwristiki[LINESIZE];
enaString = (char*)malloc(sizeof(char) * LINESIZE + 1);
if(enaString == NULL)//check if the memory has free blocks
{
printf("error to initillize memory");
exit(1);
}
printf("Eisagete xaraktira mikous %d :\n", LINESIZE);
do{
xaraktiras = getc(stdin);
enaString[ctr] = xaraktiras;
ctr++;
if (ctr == LINESIZE -1)
{
break;
}
}while(xaraktiras != '\n' );
enaString[ctr] = '\0'; //is this considered undefined behaviour?
enaString = NULL;
free(enaString);
printf("eisagete mia leksi diaxwrismou :\n");//ask the user for another word.fails cause it keeps the getc() value from before
scanf(" %15s",diaxwristiki);
diaxwristiki[LINESIZE-1] = '\0';//is this undefined behaviour?
printf("timi diaxwrismou %s\n", diaxwristiki);
}
To compile the information hidden in the comments below the OP's post and my own 2 cents:
#include <stdio.h>
#include <stdlib.h>
// you don't use anything from string.h in your version
//#include <string.h>
// Put simple constants here, for the preprocessor to process
#define LINESIZE 15
// you don't use the arguments, no need to put them here
int main( /*int argc, char *argv[] */ )
{
// such constants are better put into a preprocessor directive
//int LINESIZE = 15; //maximum length of array of characters
int ctr = 0;
// Sorted into three lines (three different types) better to read
// No need to initialize enaString to NULL for malloc/calloc
// It is a good idea to do for realloc(), safes you
// the initial malloc() but you do not use realloc() here
char *enaString;
// (f)getc() and scanf() return an int
int xaraktiras, ret_scanf;
char diaxwristiki[LINESIZE];
// no casting of malloc() in C
enaString = /*(char*) */ malloc(sizeof(char) * LINESIZE + 1);
if (enaString == NULL) //check if the memory has free blocks
{
// use stderr stream for error output
// (sderr might not be available but worth a try)
// UX-tip: use the same language for errors that you
// use for user interaction elsewhere
fprintf(stderr, "error to initillize memory");
// Use the macros from stdlib.h, the return values
// are OS dependent and might not be 0 and 1 respectively
exit(EXIT_FAILURE);
}
// you ask for a word of a certain size or only for a word?
// (My Greek is not very good and Google is of not much help here)
printf("Eisagete xaraktira mikous %d :\n", LINESIZE);
do {
// please be aware the getc() is in most cases implemented
// as a macro, use fgetc() if you are not sure if that is
// a problem (it is not here) because macros might get evaluated
// more than once
xaraktiras = getc(stdin);
// you need to check for EOF somewhere. Here would be a good place
if (xaraktiras == EOF) {
// try it by pressing CTRL+D instead of feeding characters to getc()
fprintf(stderr, "EOF found in getc() loop\n");
// EOF might also indicate an error, see the handling of scanf() below
// We don't bother with it now, we just exit
exit(EXIT_FAILURE);
}
// no need for a cast here
enaString[ctr] = xaraktiras;
// put it after the check, otherwise you have an undefined
// character at enaString[ctr]
// ctr++;
if (ctr == LINESIZE - 1) {
// You offered LINESIZE, have allocated LINESIZE+1, but only
// allow LINESIZE-1
// The user might be disappointed
break;
}
ctr++;
// No casting needed, because the type of a char constant is int
// (yes, that means that things like "char c='STOP'" once worked and you
// were able to look for 0x53544f50 in the memory dump. Some compilers might
// still allow for it but it is not recommended)
} while (xaraktiras != '\n');
// slurp the rest up if there were more characters given
// (check for EOF ommitted here but should be added, of course)
if(xaraktiras != '\n'){
while ((xaraktiras = getc(stdin)) != '\n');
}
// you go up to LINESIZE-1 now, so, together with the replacement of ctr++, it is OK
enaString[ctr + 1] = '\0'; //is this considered undefined behavior?
// don't just dump the painfully gathered characters, print them at least.
// That way you'll find out that you included the '\n', too, which
// might or might not have been your intent
printf("enaString = \"%s\"\n",enaString);
// To free the memory free() needs to know where it is and
// the pointer enaString points to that memory. If you set
// enaString to NULL free() does not know which memory to free
// (worse: free(NULL) is allowed) and the memory
// is left alone, crying, and is unreachable until the program ends,
// a so called "memory leak"
// enaString = NULL;
// free(enaString);
free(enaString);
// I don't know who told you so, but it is indeed a good idea to set
// the pointer to the free'd memory to NULL. Won't do anything here
// but might safe you from a lot of headaches in large programs
enaString = NULL;
printf("eisagete mia leksi diaxwrismou :\n"); //ask the user for another word.
// the variable "diaxwristiki" can hold 15 characters , "%15s" allows for 16, because
// scanf() includes `\0` (EOS, NUL, nul, or whatever the kids call it today), too!
// scanf() returns the number of elements (not characters!) read or EOF.
// for strerror()
#include <string.h>
// for errno
#include <errno.h>
// reset errno, just in case
errno = 0;
if ((ret_scanf = scanf(" %14s", diaxwristiki)) == EOF) {
// It also returns EOF in case of an error, so check for it
if (errno != 0) {
fprintf(stderr, "error in scanf: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
// try it by pressing CTRL+D instead of feeding characters to scanf()
fprintf(stderr, "EOF triggered by scanf()\n");
// diaxwristiki might contain rubbish at this point, clear it
diaxwristiki[0] = '\0';
}
// no need for adding EOS, scanf() already added it
// diaxwristiki[LINESIZE-1] = '\0';//is this undefined behaviour?
printf("timi diaxwrismou %s\n", diaxwristiki);
// it's "int main()", so return something.
exit(EXIT_SUCCESS);
}
I have written the following program that is intended to read a string from a file into variable "title":
#include <stdio.h>
#include <stdlib.h>
int main()
{
int m, b;
char *title;
FILE *fp;
fp = fopen("input2.txt", "r");
if (fp == NULL)
{
printf ("Error: file cannot be found\n");
return 1;
}
fscanf(fp, "<%d>\n<%d>", &m, &b);
printf("%d\n%d", m, b);
fscanf(fp, "<%s>", title);
fclose(fp);
return 0;
}
The above program crashes at the second call to fscanf. Why does this happen?
Your main problem is that you've not allocated space for the string to be read into. You can do this in multiple ways:
char title[256];
or:
char *title = malloc(256);
if (title == NULL)
{
fprintf(stderr, "Out of memory\n");
exit(1);
}
either of which should then be used with:
if (fscanf(fp, " <%255[^>]>", title) != 1)
{
fprintf(stderr, "Oops: format error\n");
exit(1);
}
or, if you have a system with an implementation of fscanf() that's compliant with POSIX 2008, you can use the m modifier to %s (or with %c, or, in this case, a scanset %[...] — more on that below):
char *title = 0;
if (fscanf(fp, " <%m[^>]>", &title) != 1) // Note the crucial &
{
fprintf(stderr, "Oops: format error\n");
exit(1);
}
This way, if the fscanf() succeeds in its entirety, the function will allocate the memory for the title. If it fails, the memory will have been released (or never assigned).
Note that I changed %s to %m[^>]. This is necessary because the original conversions will never match the >. If there is a > in the input, it will be incorporated into the result string because that reads up to white space, and > is not white space. Further, you won't be able to tell whether the trailing context was ever matched — that's the > in the original format, and it's still a problem (or not) in the revised code I'm suggesting.
I also added a space at the start of the string to match optional white space. Without that, the < at the start of the string must be on the same line as the > after the second number, assuming that the > is present at all. You should also check the return from the first fscanf():
if (fscanf(fp, "<%d>\n<%d>", &m, &b) != 2)
{
fprintf(stderr, "Oops: format error\n");
exit(1);
}
Note that the embedded newline simply looks for white space between the > and the < — that's zero or more blanks, tabs or newlines. Also note that you'll never know whether the second > was matched or not.
You could use exit(EXIT_FAILURE); in place of exit(1); — or, since this code is in main(), you could use either return 1; or return(EXIT_FAILURE); where the parentheses are optional in either case but their presence evokes unwarranted ire in some people.
You could also improve the error messages. And you should consider using fgets() or POSIX's getline() followed by sscanf() because it makes it easier (by far) to do good error reporting, plus you can rescan the data easily if the first attempt at converting it fails.
This:
char *title;
is just a pointer to a char. If fscanf writes more than one character to it, you will corrupt whatever happens to be in memory after
You need to do one of two things:
char title[50]; // Holds up to 49 characters, plus termination
Or:
#include <stdlib.h>
// ...
char *title = malloc(50 * sizeof(char)); // Same capacity as above
if (title == NULL) {
// handle out of mem error
}
// ...
free (title);
The first option is obviously much simpler, but requires you to know your array size at compile time.
If you are new to programming, and haven't encountered pointers and dynamic memory allocation yet, stick with the first option for now.
I'm kinda new in C so I don't know if im allowed do to the following "if" in C, my error happens in the if line but the error is a segmentation fault, that should be a uninitialized variable but i give her a value from a file...well, there is the code:
char t1, ch;
if((fscanf(f,"P%d", &t1)) == 1){
if(!((strcmp(t1,"P2")==0) || (strcmp(t1,"P5")==0))){ // error here
fprintf(stderr, "\nTipo de imagem invalido.\n");
fclose(f);
return NULL;
}
If anyone can help me i will appreciate...
Thank you in advance!
Edit: I have rewritten answer to be complete and correct.
You are expecting to put an integer %d into a of char, which are most likely of different sizes. int is at least same size, and often bigger, so probably this is 1st place that should have blown up.
You can look at table here to figure out what format string specifier to use to match size of the variable you are reading. For a char it should be %hhd.
Also please note you use a string comparison function strcmp for a single character. Not sure why compiler did not point out type miss-match between char* and char.
You could force it to work with &t1 but it could end up with memory access faults, as strcmp expects the input to be a null-terminated string, which for single character is valid only for an empty string '\0'.
Also. If you write a format string like "P%s" the P symbol will not appear in the output.
What you can do.
Sticking to the string is tricky.
You could read whole string, like P3, and compare strings as you do, however you need to remember that they are null terminated. So to hold P3 you need something like char t1[3]. char t1; will certainly not hold "P3".
Also, unless your input is well formed and you are sure of, it is dangerous to read string from scanf with plain %s. You never know how long the input will be and it might end up overflowing your buffer...
You should specify the length of the string to read e.g. %2s.
#include <stdio.h>
#include <stdlib.h>
#define TYPE_SIZE 2
char ch;
int size;
char t1[TYPE_SIZE+1];
int main(){
if( (scanf("%2s", &t1) == 1)){
printf("t1=%s", t1);
if(!((strcmp(t1,"P2")==0) || (strcmp(t1,"P5")==0))){ // error here
fprintf(stderr, "\nTipo de imagem invalido.\n");
return -1;
}
} else fprintf(stderr, "Wrong input.\n");
return 0;
}
Still with this if you will input P5456456, there will be no error. You would need to consume input after 1st two characters and test it. You also need to keep track of max type length. You could also change TYPE_SIZE to something extra large in advance, but it is something to look for, and buggy prone (more theoretical, but still). Whenever you change TYPE_SIZE you need to update the format string, or construct it dynamically - more code.
Personally I would probably try reading type number into an integer and construct the if vs it.
#include <stdio.h>
#include <stdlib.h>
char ch;
int size;
unsigned char t1;
int main(){
if( (scanf("P%hhu", &t1) == 1)){
printf("t1=%u\n", t1);
if(!((t1 == 2) || (t1 == 5))){ // error here
fprintf(stderr, "\nTipo de imagem invalido.\n");
return -1;
}
} else fprintf(stderr, "Wrong input.\n");
return 0;
}
You should get segmentation fault even in
if((fscanf(f,"P%d", &t1)) == 1){
You should use
int t1;
In strcmp, you are passing t1 which is char not const char *. Here, char value is being typecasted to a pointer and now it tries to access the value at the location of t1. And this results in segmentation fault. So change is
int t1;
if((fscanf(f,"P%d", t1))){
if(!((2 == t1) || (5 == t1))){ // error here
fprintf(stderr, "\nTipo de imagem invalido.\n");
fclose(f);
return NULL;
}
#include<stdio.h>
#include<stdlib.h>
typedef struct{
char cStdName[50];
int nStdNum;
char cStdClass[4];
float dStdAvg;
}student;
student* students;
int cmp(const void* a, const void* b);
void main() {
int num = 0,i=0;
FILE *f;
printf("Number of students:");
scanf("%d", &num);
students = (student*)malloc(num*sizeof(student));
for(i=0;i<num;++i){
student* ptr = students+i*sizeof(student);
printf("Name:");
scanf("%s", ptr->cStdName);
printf("Num");
scanf("%d", &ptr->nStdNum);
printf("Class:");
scanf("%s", ptr->cStdClass);
printf("Grade:");
scanf("%f", &ptr->dStdAvg);
}
f = fopen("bin.bin","wb");
fwrite(&num,sizeof(int),1,f);
fwrite(students,sizeof(student),num,f);
fclose(f);
system("pause");
}
This is supposed to output the number of students and all the structure 'array' in a binary file and it works with 1 student. But when I add >=2 people, the file looks like this:
http://i.imgur.com/LgL8fUa.png
If I add only 1 student, there is still some of this Windows path nonsense:
http://i.imgur.com/s7fm9Uv.png
It is OK though, the program that reads the file ignores everything after the NULL(I mean, for the first char array).
I think the problem is somewhere in the for() loop and the pointer juggling but I can't tell where.
student* ptr = students + i * sizeof(student);
In C, pointer arithmetic already includes sizeof(student). You will read past the end of your arrray.
student* ptr = students + i;
However, you'll notice accessing to ptr is the same as accessing to students[i].
There are a few issues with your code:
First of all, as Kirilenko said, you should use students[i] in your code. In your case, students + i * sizeof(student) goes out of bounds.
It's never a good idea to use fwrite with an array of structs. That's because the compiler may add some space between the members of a struct (padding), which means that when you pass an array of structs to fwrite, the padding bytes (which will contain garbage) will be printed.
The same also applies to the char array members in your struct. All unused bytes will contain garbage, which will be printed when you use fwrite. It's better to use strlen to determine how many read characters each char array contains.
Here's how I'd write the students' array to a file:
void write(students* array, int len, FILE* out)
{
int i;
fwrite(len, 1, sizeof(len), out);
for (i = 0; i < len; i++){
fwrite(array[i]->cStdName, 1, strlen(array[i]->cStdName), out);
fwrite(array[i]->nStdNum, 1, sizeof(array[i]->nStdNum), out);
fwrite(array[i]->cStdClass, 1, strlen(array[i]->cStdClass), out);
fwrite(array[i]->dStdAvg, 1, sizeof(array[i]->dStdAvg), out);
}
}
Kirilenko's answer should solve your immediate problem, but there are errors ranging from trivial to severe on nearly every line of this program. Depending on what your larger goal is, you might not need to fix all of them, but I'm going to write them all down anyway, to illustrate the size of the iceberg.
void main() {
int main(void). The int is an absolute requirement. In C (not C++) writing () for a function argument list means the arguments are unspecified, not that there are no arguments; technically you can get away with it here because this is a function definition, but it's bad style.
The opening curly brace of a function definition always goes on a line by itself, even if all other opening braces are cuddled.
int num = 0,i=0;
Inconsistent spacing. Initializations are unnecessary.
printf("Number of students:");
Some style guides prefer fputs("Number of students", stdout); when you're not using printf's formatting capabilities. But some compilers can do the transformation for you, and it's not a big deal.
scanf("%d", &num);
Never use scanf, fscanf, or sscanf, because:
Numeric overflow triggers undefined behavior. The C runtime is allowed to crash your program just because someone typed too many digits.
Some format specifiers (notably %s, which you use later in this program!) are unsafe in exactly the same way gets is unsafe, i.e. they will cheerfully write past the end of the provided buffer and crash your program (this particular program doesn't look security-sensitive to me, but one should always code as if one's programs are at least somewhat dangerous that way).
They make it extremely difficult to handle malformed input correctly.
The correct way to read a single nonnegative number from the user is like this:
unsigned long getul(void)
{
char buf[80], *endp;
unsigned long val;
for (;;) {
fgets(buf, 80, stdin);
val = strtoul(buf, &endp, 10);
if (endp != buf && *endp == '\n')
return val;
if (buf[strlen(buf)] != '\n')
while (getchar() != '\n')
/* discard */;
fprintf(stderr, "*** Enter one nonnegative integer, smaller than %lu.\n",
ULONG_MAX);
}
}
You can get away with a fixed-size buffer here because nobody's ULONG_MAX is so large that it doesn't fit in 80 characters.
students = (student*)malloc(num*sizeof(student));
You should use calloc here, so that when you go to write your structures to disk, they aren't full of junk. (Or write custom serializers as suggested by Alexandros, that will also avoid the problem.)
for(i=0;i<num;++i){
Preferred style is for (i = 0; i < num; i++) {. In addition to the spacing, use i++ instead of ++i so that i appears in the same position in all three expressions; this makes it easier to read.
student* ptr = students+i*sizeof(student);
student* ptr = students + i; as discussed elsewhere.
printf("Name:");
scanf("%s", ptr->cStdName);
See comments above. You want another helper function, like so:
void getstr(char *buf, size_t len)
{
size_t n;
for (;;) {
fgets(buf, len, stdin);
n = strlen(buf);
if (n < len && buf[n] == '\n') {
memset(buf+n, 0, len-n);
return;
}
while (getchar() != '\n')
/* discard */;
fprintf(stderr, "*** Enter no more than %lu characters.",
(unsigned long)(len-1));
}
}
...
scanf("%f", &ptr->dStdAvg);
And here you need another helper function. getf is exactly the same as getul except it uses strtod, and of course the error message is a little different.
f = fopen("bin.bin","wb");
Isn't that an awfully generic file name? There should probably be a way for the user to specify it.
fwrite(&num,sizeof(int),1,f);
Your file format needs a magic number.
fwrite(students,sizeof(student),num,f);
You are writing binary data to disk in CPU-endian order. That may not be a problem for this application, but be aware that you might have a cross-platform compatibility headache down the road. (Personally, for what it looks like you're doing, I would use a textual serialization such as JSON, or a simple no-daemon database such as sqlite.) See Alexandros' answer for more potential problems with this file format.
It's rarely a problem when writing to files on disk, and this isn't the sort of program whose output gets piped somewhere, but still, I'm'a mention that fwrite does not guarantee to write all the data you provide it. Technically you have to call fwrite in a loop like this:
size_t r, n = sizeof(student) * num;
char *p = (char *)students;
while (n > 0) {
r = fwrite(p, 1, n, f);
if (r == 0) break; /* write error */
n -= r;
p += r;
}
For this to work correctly you must do the multiplication yourself and pass 1 for the second argument to fwrite; otherwise a short write may end in the middle of an "element of data" and you have no way of knowing that this has happened.
fclose(f);
Check for write errors before closing the file.
if (ferror(f) || fclose(f)) {
perror("bin.bin");
return 1; /* unsuccessful exit */
}
...
system("pause");
Just return 0. Programs that make you hit a key to exit are batch-unfriendly.
The pointer assignment can be outside for loop once and you can increment the pointer at the end of for loop. Try this.
Ok. Here is an attempt to explain what is happening, the ptr is pointing to first element in students list of struct types and when you increment the ptr at the end of the for loop, it points to the next student in the list.
---
students = (student*)malloc(num*sizeof(student));
student* ptr = students;
for(i=0;i<num;++i){
printf("Name:");
----
----
ptr++;
}