-2

So I've got a project I'm working on in ANSI C (C89) for class. I've gotten stuck in the process. I'm getting a

segmentation fault: 11

issue, and no matter what I look up, I can't seem to solve the issue. can someone look at my code and point me in the right direction?

/* CS315 Lab 3: C data types */

#include <stdio.h>
#include <stdlib.h> /*added this to provide a declaration for malloc*/

#define TENMB 1048576 /*1024 kilobytes or 10 megabytes */
#define ONEB 1

FILE * fp = NULL;

End(FILE * fp)/*made END a function */
{ 
    fclose(fp);             /* close and free the file */
    exit(EXIT_SUCCESS);     /* or return 0; */
}

initialize(int argc, char ** argv)
{
    /* Open the file given on the command line */
    if( argc != 2 )
    {
        printf( "Usage: %s filename.mp3\n", argv[0] );
        return(EXIT_FAILURE);
    }
    FILE * fp = fopen(argv[1], "rb");
    if( fp == NULL )
    {
        printf( "Can't open file %s\n", argv[1] );
        return(EXIT_FAILURE);
    }
    return 0; /*this might need to change */
}

readFile(FILE * fp)
{
    /* How many bytes are there in the file?  If you know the OS you're
    on you can use a system API call to find out.  Here we use ANSI
    standard function calls. */
    long size = 0;
    fseek(fp, 0, SEEK_END );        /* go to 0 bytes from the end */
    size = ftell(fp);               /* how far from the beginning? */
    rewind(fp);                     /* go back to the beginning */

    if( size < ONEB || size > TENMB ) 
    {
        printf("File size is not within the allowed range\n"); 
        End(fp); /* switched from goto END:*/
    }

    printf( "File size: %.2ld MB\n", size/TENMB );  /* change %d to %ld, added .2 to print to 2 decimal places (maybe use f instead) */
    /* Allocate memory on the heap for a copy of the file */
    unsigned char * data = (unsigned char *)malloc(size); 
    /* Read it into our block of memory */
    size_t bytesRead = fread( data, sizeof(unsigned char), size, fp );
    free(data); /* deallocation */
    if( bytesRead != size )
    {
        printf( "Error reading file. Unexpected number of bytes read:     %zu\n",bytesRead ); /* changed from %d to %zu */
        End(fp); /* switched from goto END:*/
        return 0;
     }
    return 0;
}

int main( int argc, char ** argv )
{
    initialize(argc, argv);

    readFile(fp);

    /* We now have a pointer to the first byte of data in a copy of the      file, have fun
    unsigned char * data    <--- this is the pointer */
}

Thanks for any help!

  • 2
    Have you used a debugger? At the very (very) least the debugger will tell you immediately which line of code triggers the seg fault. – kaylum Nov 09 '16 at 00:26
  • No, in the case that I get the segfault, I'm using a file thats 5mb, which is in between those values. – Michael Brown Nov 09 '16 at 00:32
  • 3
    `TENMB` is actually 1MB .. you need to add a 0 – yano Nov 09 '16 at 00:33
  • Oh shoot! You're right. Thank you. I'm still getting the segfault issue though. – Michael Brown Nov 09 '16 at 00:39
  • 2
    `FILE * fp = fopen(argv[1], "rb");` --> `fp = fopen(argv[1], "rb");` – BLUEPIXY Nov 09 '16 at 00:41
  • YES! thank you. I guess I was overwriting the global pointer variable by restating it? Now, it just returns 00MB, which is not right at all.. – Michael Brown Nov 09 '16 at 00:47
  • @MichaelBrown, hi - it is unrelated to your problem, but a coding tip - normally, one of the common use cases of #define is to name a constant that might change in the future, however the way you are naming ONEB and TENMB is defeating that purpose. If you change the value, you will probably need to change the naming too (e.g. TWENTYMB) and replace every use of the defined value. I would recommend LOWER_LIM and UPPER_LIM or something similar, to avoid linking the value to the name. =) – BlueStrat Nov 09 '16 at 00:49
  • `size > TENMB` .. range error. So if `size < TENMB`, `size/TENMB` is `0` – BLUEPIXY Nov 09 '16 at 00:53
  • @MichaleBrown, does your program crash before it prints out the file size? – BlueStrat Nov 09 '16 at 00:55
  • No. It prints out: File size: 00MB. which makes me think that its something to do with what I'm passing in and out of the function calls. – Michael Brown Nov 09 '16 at 01:02
  • As @BLUEPIXY pointed out, 5MB / 10MB is 0MB with integer division. – ad absurdum Nov 09 '16 at 01:08
  • Oh right, so I've changed size to type float, and now I'm getting an output of .55MB, which is not right for the file I'm using. It should be 5.7MB. I know there's some weird stuff with floats and coercion. – Michael Brown Nov 09 '16 at 01:17
  • 1
    Is this supposed to be C89? Title says "Yes", but some aspects of code say "No." – ad absurdum Nov 09 '16 at 01:58
  • Your sizing problem is because you divide `size` by 10MB instead of 1MB. – ad absurdum Nov 09 '16 at 02:00
  • Yes, it is supposed to be. What parts aren't? This lab is particularly frustrating, I mean, there are numerous reasons its been obsolete for 15 years. And thanks for pointing that out, I had since figured that out. The source code just had integer constants, which only differed by a single 0. – Michael Brown Nov 09 '16 at 03:49
  • 1. This is not a 'debug y code for free' service! 2. This code doesn't even compile (all functions except `main` are missing return-value)! – barak manos Nov 09 '16 at 05:13

2 Answers2

0

You say that this should be C89 code, but there are some aspects of this code that are not C89 compliant. I have included a modified version of your code below that fixes these things, compiles without warnings, and seems to run correctly. By way of a disclaimer, I never write in C89, so someone else may take issue with something here.

The segfault that you report is due to the double-declaration of fp, as pointed out by @BLUEPIXY in the comments to your question. You declare this file pointer first at file scope (i.e., fp is a "global" variable), and then at block scope within the function initialize(). The second declaration hides the first one within the block, so you open a file in initialize() and assign the resulting pointer to the block scope fp (which has automatic storage duration). When main() then calls readFile(), it is the file scope fp that is passed, and this fp is initialized to NULL. So fseek(fp, 0, SEEK_END) is called on a NULL pointer, and this causes the segfault. Changing the second declaration to an assignment fixes this problem:

fp = fopen(argv[1], "rb");

You can't mix variable declarations and code in C89, so you need to move the declarations to the beginning of the functions. Also, C89 does not support the %zu format specifier for size_t variables. Instead, use %lu and cast the size_t value bytesRead to (unsigned long).

I added a definition: ONEMB 1048576, and removed the TENMB definition. In the calculation of the file size, you were dividing the number of bytes by 10MB instead of 1MB, and I take it that you were in fact trying to calculate the number of MB.

I added a check for errors to the call to ftell(). Since ftell() returns a long, I added a size_t fileSize variable and cast the value of size to size_t before assigning it to fileSize. I changed theprintf() statement to:

printf( "File size: %.2f MB\n", (fileSize * 1.0)/ONEMB );

so that the file size is reported with more precision; this way, files smaller than 1 MB will not be reported as having a size of 0 MB.

I removed the cast from the call to malloc(), as it is absolutely not needed in C.

The assignment to bytesRead calls fread(), which takes a size_t parameter for the third argument, not a long. You originally had the long value size here, and this is one reason for the new fileSize variable. The following line had a comparison between bytesRead, which is size_t, and size. This should generate compiler warnings (you do have them on, don't you?) as using signed and unsigned types in the same expression can lead to difficulties, and so here I used the fileSize variable again.

You were missing the return statement at the end of main(), and your function definitions were missing their return type specifiers, so I also added these. I also noticed that your program was segfaulting when invoked with no arguments. This is because on errors in your initialize() function, you were returning to the calling function, which then called readFile() with a NULL pointer. You should instead exit() from the program. I have made these changes to all file error traps in the modified code.

There are some other issues with your code. I would prefer not to use the End() function. It does not test for errors during closing files, and will segfault if you pass in a NULL pointer. Also, the "Unexpected number of bytes read" error in readFile() will end up exiting successfully, since End() always exits with EXIT_SUCCESS.

I don't love the casting from long to size_t that I did, and someone else may have a better approach. What I was trying to manage here was that ftell() returns a long, which you use to calculate the file size, and fread() both takes and returns size_t values.

Most of what I did was in the readFile() function, and I removed your comments from this function to make it easier to see what was changed.

I compiled with:

gcc -std=c89 -Wall -Wextra -pedantic

#include <stdio.h>
#include <stdlib.h> /*added this to provide a declaration for malloc*/

#define ONEMB 1048576
#define ONEB 1

FILE * fp = NULL;

void End(FILE * fp)/*made END a function */
{ 
    fclose(fp);         /* close and free the file */
    exit(EXIT_SUCCESS);     /* or return 0; */
}

int initialize(int argc, char ** argv)
{
    /* Open the file given on the command line */
    if( argc != 2 )
    {
        printf( "Usage: %s filename.mp3\n", argv[0] );
        exit(EXIT_FAILURE);
    }
    fp = fopen(argv[1], "rb");
    if( fp == NULL )
    {
        printf( "Can't open file %s\n", argv[1] );
        exit(EXIT_FAILURE);
    }
    return 0; /*this might need to change */
}

int readFile(FILE * fp)
{
    long size = 0;
    unsigned char *data;
    size_t fileSize, bytesRead;
    fseek(fp, 0, SEEK_END );

    if ((size = ftell(fp)) == -1) {
        fprintf(stderr, "ftell() error\n");
        exit(EXIT_FAILURE);
    };
    rewind(fp);

    if( size < ONEB || size > 10 * ONEMB ) 
    {
        printf("File size is not within the allowed range\n"); 
        End(fp);
    }

    fileSize = (size_t) size;
    printf( "File size: %.2f MB\n", (fileSize * 1.0)/ONEMB );

    data = malloc(fileSize);

    bytesRead = fread( data, sizeof(unsigned char), fileSize, fp );
    free(data);

    if( bytesRead != fileSize )
    {
        printf( "Error reading file. Unexpected number of bytes read:     %lu\n", (unsigned long) bytesRead );
        End(fp);
        exit(EXIT_FAILURE);
     }

    return 0;
}

int main( int argc, char ** argv )
{
    initialize(argc, argv);

    readFile(fp);

    /* We now have a pointer to the first byte of data in a copy of the      file, have fun
    unsigned char * data    <--- this is the pointer */

    End(fp);   
    return 0;
}
Community
  • 1
  • 1
ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • `%lu` is not the correct specifier for `size_t`. Cast the result to `unsigned long` before printing or you'll invoke undefined behavior. For example `long` on 64-bit Windows is not the same size as `size_t` – phuclv Nov 09 '16 at 06:21
  • @Lưu Vĩnh Phúc-- Of course! I missed that, but it is fixed now. I am not used to C89, so if you see anything else that I overlooked, please comment. – ad absurdum Nov 09 '16 at 06:29
0

In your code you have declared fp twice, both globally and locally

FILE * fp = NULL;

End(FILE * fp)/*made END a function */
{ 
    fclose(fp);             /* close and free the file */
    exit(EXIT_SUCCESS);     /* or return 0; */
}

initialize(int argc, char ** argv)
{
...
    FILE * fp = fopen(argv[1], "rb");
...

Even though you are writing C89 there is no need to pick up the bad things with the standard. E.g. declare functions properly with a return type.

AndersK
  • 35,813
  • 6
  • 60
  • 86