1

I am novice to C programming and I have written a code to a requirement specification but I am consistently getting Segmentation Fault and unable to proceed ahead. If the file name is 'code.c' and it runs with an error of not passing the argument (filename). But if the filename is passed, we land in Segmentation Fault. Any help/suggestions will be appreciated.

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



struct _data 
{               
   char *firstName;
   char *lastName;
   long number;
}; 

// SCAN FILE
int SCAN(FILE *(*stream))
{
    *stream = fopen("inputFile.data", "r");

    int ch = 0, lines = 0;

    while (!feof(*stream))
    {
        ch = fgetc(*stream);
        if (ch == '\n')
        {
            lines++;
        }
    }
    return lines;
}

// LOAD FILE
struct _data *LOAD(FILE *stream, int size) 
{
    int i;
    size_t chrCount;
    char *text, *number, *firstName, *lastName;
    struct _data *BlackBox;

    if ((BlackBox = (struct _data*)calloc(size, sizeof(struct _data))) == NULL) 
    {
          printf("ERROR - Could not allocate memory.\n");
          exit(0);
    }

    rewind(stream);


    for (i = 0; i < size; i++) 
    {
        getline(&text, &chrCount, stream);
        firstName   = strtok(text, " ");
        lastName    = strtok(text, " ");
        number      = strtok(NULL, "\n");

        // Allocate memory for name part of struct.
        if ((BlackBox[i].firstName = (char*)calloc(strlen(firstName), sizeof(char))) == NULL) 
        {
           printf("ERROR - Could not allocate memory.\n");
           exit(0);
        }
        if ((BlackBox[i].lastName = (char*)calloc(strlen(lastName), sizeof(char))) == NULL)
        {
           printf("ERROR - Could not allocate memory.\n");
           exit(0);
        }

        strcpy(BlackBox[i].firstName, firstName);
        strcpy(BlackBox[i].lastName, lastName);
        BlackBox[i].number = atol(number);
    }
    fclose(stream);
    return BlackBox;
}


void SEARCH(struct _data *BlackBox, char *name, int size, int inputs) 
{
    int i;
    int found = 0;
    char *search = " ";
    char *firstName;
    char *lastName; 

    if (inputs == 2)
    {
        firstName = strtok(name, search);
        lastName = strtok(NULL, search);
    }


    printf("*******************************************\n");
    if (inputs == 2)
    {   
        for (i = 0; i < size; i++) 
        {          
            if (!strcasecmp(firstName, BlackBox[i].firstName) && !strcasecmp(firstName, BlackBox[i].firstName))
            {
                printf("The name was found at the %d entry.\n", i);
                found = 1;
                break;
            }
        }
    }
    else
    {
        for (i = 0; i < size; i++) 
        {          
            if (!strcasecmp(firstName, BlackBox[i].firstName) || !strcasecmp(firstName, BlackBox[i].firstName))
            {
                printf("The name was found at the %d entry.\n", i);
                found = 1;
                break;
            }
        }
    }

    if (found == 0) 
    {
          printf("The name was NOT found.\n");
    }
    printf("*******************************************\n");
}

// FREE MEMORY
void FREE(struct _data *BlackBox, int size) 
{
    int i;
    for (i = 0; i < size; i++) 
    {
        free(BlackBox[i].firstName);
        free(BlackBox[i].lastName);
    } 
    free(BlackBox);
    BlackBox = NULL;
}


// MAIN
int main(int argv, char **argc) 
{
    int size;
    FILE *stream;
    struct _data *BlackBox;

    // argv == 1 WORKS, Below message is printed.
    if (argv == 1) 
    {          
        printf("*******************************************\n");
        printf("* You must include a name to search for.  *\n");
        printf("*******************************************\n");
    }
    // argv == 2 DOES NOT WORK, Segmentation Fault.     
    if (argv == 2) 
    {
        size = SCAN (&stream);
        BlackBox = LOAD(stream, size);
        SEARCH(BlackBox, argc[1], size, 1);
    }
    if (argv == 3) 
    {
        size = SCAN(&stream);
        BlackBox = LOAD(stream, size);
        SEARCH(BlackBox, argc[2], size, 2);
    }
    return 0;
}
user3337714
  • 663
  • 1
  • 7
  • 25
  • 3
    You never check if `fopen()` succeeded! And [`while (!feof(file))`](http://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong) is always wrong. – Iharob Al Asimi Apr 08 '15 at 22:12
  • Did you try running `pstack` on the core file left behind when it crashed (assuming you have your core dump settings configured properly on your system), or run it in a debugger such as gdb so when it crash you can learn the line #? When a program segv's first thing you need to do is figure out the line it occurs on. – clearlight Apr 08 '15 at 22:13
  • Actually `pstack ` works on Solaris but might not on Linux, but this should: `gdb path/to/corefile/` `(gdb) where` `(gdb) thread apply all bt` – clearlight Apr 08 '15 at 22:14
  • `if ((BlackBox[i].firstName = (char*)calloc(strlen(firstName),` +1 for NUL – BLUEPIXY Apr 08 '15 at 22:17
  • @iharob I did not because the file was with me and this is not an enterprise code. I do understand it is wrong but IS that causing the SF fault? – user3337714 Apr 08 '15 at 22:20
  • @Allaboutthatbase2 I do not know how to run pstack or gdb? But I can try. Also what can be the cause for SF here? – user3337714 Apr 08 '15 at 22:21
  • @JamesWilkins So I do need to make some specific change to my code and will that correct the SF fault – user3337714 Apr 08 '15 at 22:22
  • `if (!strcasecmp(firstName,` : `firstName` is uninitialized when parameter of `input` == 1. – BLUEPIXY Apr 08 '15 at 22:29
  • Do not give your functions all uppercase names, it is classically reserved for macro names. – chqrlie Apr 08 '15 at 22:31
  • Change `(char*)calloc(strlen(lastName), sizeof(char))` to `strdup(lastName)` and the same for the other string duplication, and get rid of the `strcpy()` below that. Also note that `lastName = strtok(text, " ");` should take `NULL` instead of `text`. – chqrlie Apr 08 '15 at 22:37
  • @iharob you are taking it personal. I am novice and SF error has not relation to check's of `fopen()` or `while(!feof(file))` instead of helping. You are simply criticizing. I am sorry to offend you. – user3337714 Apr 08 '15 at 22:43
  • @JamesWilkins I made the changes to the `size` but still `SF` error. :( – user3337714 Apr 08 '15 at 22:45
  • Besides the fact that the last line might not have a new line character, you also forgot that strcpy copies the null char, so `calloc(strlen(firstName),...` does not create a large enough string buffer (same goes for 'lastName'). – James Wilkins Apr 09 '15 at 06:37

3 Answers3

0

You have a problem in this code:

    firstName   = strtok(text, " ");
    lastName    = strtok(text, " ");
    number      = strtok(NULL, "\n");

...

    BlackBox[i].number = atol(number);

The second strtok() call should pass NULL as its first argument. As it is, the third strtok() call is certain to return NULL because the first call modifies text in such a way that the second consumes the whole thing (when tokenizing again from the beginning, as it erroneously does). You do not test for that, however, and as a result, atol() attempts to dereference a null pointer.

Update:

Additionally, as @chqrlie and later @JamesWilkins observed, you do not allocate sufficient space for BlackBox[i].firstName and BlackBox[i].lastName, as you need room for the string terminators as well. This is an entirely separate problem that could also produce a segfault. I like @chqrlie's suggestion to switch to strdup(), but it would be sufficient to just increase each allocation by one unit.

Update 2:

Furthermore, you have an issue with this line:

    getline(&text, &chrCount, stream);

You do not initialize variable text before the first call, so it contains a junk value. The function allocates a buffer only when its first argument points to a NULL pointer; otherwise it writes the line to the buffer pointed to by the pointer obtained by dereferencing the first argument. Writing to a random location in memory certainly produces undefined behavior, which in practice often manifests as a segfault.

Moreover, unless you can rely on no line of the file being longer than the first, you also need to free the text pointer at the end of each loop iteration AND reset its value to NULL, so that getline() allocates a fresh buffer on the next iteration. If you do not free it on each iteration, then you need instead to free it after the end of the loop; else you will leak memory.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • I am trying to read a file which contains line in this format "John Doe 777777" and I am trying to split them in the form of `firstName, lastName, number`. What would be the best way to correct this situation. Thank you. – user3337714 Apr 08 '15 at 22:27
  • I told you. The second `strtok()` call must pass `NULL` as its first argument. That is how you instruct it to return the *next* token instead of tokenizing again from the beginning. As a secondary -- but nevertheless important -- matter, you should always check the return value of `strtok()` in order to handle errors in the input data more gracefully than by crashing. – John Bollinger Apr 08 '15 at 22:30
  • I have made the changes. `lastName = strtok(NULL, " ");` and I still get the same error. – user3337714 Apr 08 '15 at 22:35
  • That's because you have more errors than that, as others have observed. I have updated my answer to call out two more. – John Bollinger Apr 09 '15 at 14:49
0

Try this (though I'm using Visual Studio on Windows). I added code to check for a missing '\n' on the last line, and also allowed for a variable number of search terms. I also increased the memory allocation for strings by 1 to account for the null terminating character. I noticed you are using getline(const char*..., which I think is GNU (Linux?), so I change that to fgets() just so I could compile and test it in VS (so you can change it back if you like). I put in various null checks as well, to be safer.

#include <iostream>

using namespace std;

struct _data
{
    char *firstName;
    char *lastName;
    long number;
};

// SCAN FILE
int SCAN(FILE *(*stream))
{
    *stream = fopen("inputFile.data", "r");
    if (*stream == NULL)
    {
        perror("Error opening file");
        return 0;
    }

    char ch = 0, lines = 0, linesize = 0;

    while ((ch = fgetc(*stream)) != EOF)
    {
        if (ch == '\n')
        {
            lines++;
            linesize = 0;
        }
        else linesize++;
    }

    if (linesize > 0)
        lines++; // (last line doesn't have '\n')

    return lines;
}

// LOAD FILE
struct _data *LOAD(FILE *stream, int lineCount)
{
    int i;
    size_t chrCount = 256;
    char text[256], *result, *number, *firstName, *lastName;
    struct _data *BlackBox;

    if ((BlackBox = (struct _data*)calloc(lineCount, sizeof(struct _data))) == NULL)
    {
        printf("ERROR - Could not allocate memory.\n");
        exit(0);
    }
    else memset(BlackBox, 0, sizeof(struct _data) * lineCount); // (make sure all data members are null to begin)

    rewind(stream);


    for (i = 0; i < lineCount; i++)
    {
        result = fgets(text, chrCount, stream);
        if (result == NULL)
            break; // (EOF)

        firstName = strtok(text, " ");
        lastName = strtok(NULL, " ");
        number = strtok(NULL, "\n");

        // Allocate memory for name part of struct.
        if ((BlackBox[i].firstName = (char*)calloc(strlen(firstName) + 1, sizeof(char))) == NULL)
        {
            printf("ERROR - Could not allocate memory.\n");
            exit(0);
        }
        if ((BlackBox[i].lastName = (char*)calloc(strlen(lastName) + 1, sizeof(char))) == NULL)
        {
            printf("ERROR - Could not allocate memory.\n");
            exit(0);
        }

        strcpy(BlackBox[i].firstName, firstName);
        strcpy(BlackBox[i].lastName, lastName);
        BlackBox[i].number = atol(number);
    }

    fclose(stream);

    return BlackBox;
}


void SEARCH(struct _data *BlackBox, char **names, int lineCount, int inputs)
{
    int i, l;
    int found = 0;

    printf("*******************************************\n");

    for (i = 0; i < inputs; ++i)
    {
        for (l = 0; l < lineCount; ++l)
        {
            if (BlackBox[l].firstName != NULL && !_stricmp(names[i], BlackBox[l].firstName)
                || BlackBox[l].lastName != NULL && !_stricmp(names[i], BlackBox[l].lastName))
            {
                printf("The name was found on line %d.\n", 1 + l);
                found = 1;
                break;
            }
        }
        if (found) break;
    }

    if (!found)
        printf("The name was NOT found.\n");

    printf("*******************************************\n");
}

// FREE MEMORY
void FREE(struct _data *BlackBox, int lineCount)
{
    int i;
    for (i = 0; i < lineCount; i++)
    {
        if (BlackBox[i].firstName != NULL)
            free(BlackBox[i].firstName);
        if (BlackBox[i].lastName != NULL)
            free(BlackBox[i].lastName);
    }
    free(BlackBox);
}


// MAIN
int main(int argc, char **argv)
{
    int lineCount;
    FILE *stream;
    struct _data *BlackBox;

    // argc == 1 WORKS, Below message is printed.
    if (argc == 1)
    {
        printf("*******************************************\n");
        printf("* You must include a name to search for.  *\n");
        printf("*******************************************\n");
    }
    // argc == 2 DOES NOT WORK, Segmentation Fault.     
    if (argc > 1)
    {
        lineCount = SCAN(&stream);
        if (lineCount > 0)
        {
            BlackBox = LOAD(stream, lineCount);
            SEARCH(BlackBox, argv + 1, lineCount, argc - 1);
            FREE(BlackBox, lineCount);
        }
    }
    return 0;
}

Tested it on the command line, and it works.

James Wilkins
  • 6,836
  • 3
  • 48
  • 73
-2

The problem is the argv and argc. argc is supposed to be an int (think argument count), while argv is meant to be char**. You have them mixed up in your main.

moosefoot
  • 133
  • 3
  • 11
  • 1
    This is unconventional but it is correct code. The parameters to `main` do not have special names, you could call them `donald` and `sean` if you wanted – M.M Feb 25 '17 at 05:59
  • I see, thank you for pointing that out. Regardless, it is better to follow conventions in cases like this (especially when using the names `argc` and `argv`). It just makes the code more readable for others. – moosefoot Feb 25 '17 at 22:55