0

So I'm trying to make a program that takes in certain number of strings from stdin and then output into a text file. The code I have so far is:

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

int cmpstr(const void * a, const void *b){
    const char* aa = (const char*) a;
    const char* bb = (const char*) b;
    return strcmp (aa, bb);
}

int main(int argc, char *argv[]){
  int i =0;
  int scount;
  char ** data = NULL;
  FILE * ofile;




if (argc != 3){
printf("%s \n", "The format to use this is: mySort <#of strings> <output filename>",argv[0]);
exit(EXIT_FAILURE);
}

scount = atoi(argv[1]);
if(!scount){
printf("%s \n", "Invalid number.");
exit(EXIT_FAILURE);
}

data = (char **) malloc(scount * sizeof(char*));
if(NULL == data){   
printf("Memory allocation failed\n");
exit(EXIT_FAILURE);
}

for(i = 0; i< scount; i++){
if(NULL == fgets(data[i], (int) sizeof(data), stdin)){
printf("Could not get line\n");
exit(EXIT_FAILURE);
}
}

qsort(data, scount, sizeof(char*), cmpstr);

ofile = fopen(argv[2], "w+");
if(ofile == NULL){
printf("Could not open output file. \n");
}   

for(i = 0; i<scount; i++){
    fputs(data[i], ofile);
}

fclose(ofile);

for(i=0; i<scount; i++){
    if(data[i]) free(data[i]);
}

if (data) free(data);

exit (EXIT_SUCCESS);

return 0;
}

However, when I compiled it it gave me a segmentation fault. I tried using the gdb debugger to try and debug it but it did not give me anything really and I barely understand how to use gdb. But my takeaway from the usage of gdb is that there is not enough memory allocated which confuses me since I allocated memory using malloc.

ZCode
  • 11
  • 4
  • When you compiled it segfaulted? Or after you compiled and then ran it? – sashang Nov 24 '17 at 07:02
  • segmentation fault happened after I compiled it and ran it – ZCode Nov 24 '17 at 07:03
  • Which line caused the segfault? – klutt Nov 24 '17 at 07:04
  • 6
    @ZCode ask yourself, where does `data[i]` point to in your fgets line? – PeterT Nov 24 '17 at 07:04
  • `const char* bb = (const char*) a;` is odd. – chux - Reinstate Monica Nov 24 '17 at 07:04
  • Totally missed that... But the pointers are still to `char *` not `char` – David C. Rankin Nov 24 '17 at 07:05
  • What warnings do you get if you compile with -Wall -Wextra? – klutt Nov 24 '17 at 07:06
  • OT why is cmpstr declared inside main? The second should be b, not a, BTW. – Bob__ Nov 24 '17 at 07:06
  • 1
    @chux yet, had to reevaluate, the pointers are pointers to `char *` so you need something similar to `return strcmp (*(char * const *)a, *(char * const *)b);` Additionally, the `'{'` scope is all mucked up. C doesn't 'allow nested functions. – David C. Rankin Nov 24 '17 at 07:07
  • @Bob__ -- not OT at all. That's a big part of "Why won't this compile?" – David C. Rankin Nov 24 '17 at 07:10
  • @PeterT Does data[i] not point to the newly allocated memory? Unless its pointing to NULL – ZCode Nov 24 '17 at 07:15
  • This is a goodness only knows how many times duplicate. When you sort an array of `int`, the comparator is passed two `int *` values disguised as `void *`. When you sort an array of `char *`, the comparator is passed two `char **` values disguised as `void *`. You need to fix `cmpstr()`: `int cmpstr(const void * a, const void *b) { const char* aa = *(char **)a; const char* bb = *(char **)b; return strcmp(aa, bb); }` – Jonathan Leffler Nov 24 '17 at 07:20
  • Note the comments attached to the second answer in the duplicate — added while deciding whether to use it as the duplicate for this question (and echoing my previous comment here). – Jonathan Leffler Nov 24 '17 at 07:39
  • @JonathanLeffler The proposed duplicates are about the second problem. But before reaching the second problem, there's the first problem, which has nothing to do with sorting. – Gilles 'SO- stop being evil' Nov 24 '17 at 07:43
  • @Gilles: You mean that there are also other problems, not just the sorting code which is bogus because of the faulty comparator. I suppose that shouldn't be a surprise, but when it is clear that the comparator is wrong, I like to think that it is the cause of the trouble. – Jonathan Leffler Nov 24 '17 at 07:45
  • @JonathanLeffler I mean that the faulty comparator is never even called, since the memory for the data that's to be compared isn't allocated. So whether you like it or not, it isn't what ZCode has been struggling with and trying to understand in gdb so far. It's ridiculous to offer those questions as duplicates: they won't help ZCode solve their problem. – Gilles 'SO- stop being evil' Nov 24 '17 at 07:48
  • @JonathanLeffler And yes, over the years somebody has probably posted a question about running a similar problem while allocating an array of strings. If you can find it (I couldn't), then close this question as a duplicate of *that*. – Gilles 'SO- stop being evil' Nov 24 '17 at 07:51

1 Answers1

1
data = (char **) malloc(scount * sizeof(char*));

Here you allocate memory for an array of pointers. You never initialize the contents of that array. Therefore, when you access data[0] below by passing it to fgets, you're accessing an uninitialized pointer object. If you're lucky, the contents of that uninitialized memory constitutes an invalid address, and when fgets attempts to store data there, your program crashes. If you're unlucky, the contents of that uninitialized memory happens to be the address of some block of memory that's used by some other object and you get memory corruption that's really hard to debug.

You need to initialize the pointers allocated by malloc. Like any other pointer object, depending on what you want to do, you can initialize them to NULL, to a pointer to an existing object, or to the result of calling a function such as malloc. In this program, you need to obtain storage for the lines that you're going to read, therefore you'll need to call malloc on each of the lines. Since you don't know in advance how long a line will be, this is best done at the time you read the line.

It would be a good idea to first set all the elements to NULL, as soon as you've allocated the array of pointers, and later allocate memory for the individual lines. You don't have to, but it's easier then to keep track of which elements of the array have been initialized and which ones haven't. In particular, that lets you call free on all the array elements without having to worry how many you've already reached.

fgets(data[i], (int) sizeof(data), stdin)

Passing sizeof(data) here doesn't make sense. The variable data is a pointer to char*, so sizeof(data) is just the size of a pointer. It isn't the size of the array that the pointer points to: that size isn't known at compile time, it's the argument you pass to malloc. And even that size is not relevant here: the size is the maximum number of lines you can read (multiplied by the size of a pointer to a line's contents), but what fgets needs is the size of the memory that's allocated for the line.

To keep things simple, let's say you have a maximum line length max_line_length.

data = (char **) malloc(scount * sizeof(char*));
if (data == NULL) ... // omitted error checking
for (i = 0; i < scount; i++)
    data[i] = NULL;
for (i = 0; i < scount; i++) {
    data[i] = malloc(max_line_length+2); // +2 for line break character and null byte to terminate the string
    if (data[i] == NULL) ... // omitted error checking
    if(NULL == fgets(data[i], max_line_length, stdin)) ... // omitted error checking
    ...
}

After this you'll run into another issue as described in the comments, in that cmpstr receives pointers to pointers to line contents, not pointers to line contents. This is explained in How to qsort an array of pointers to char in C?

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
  • If you want to initialize the pointers to NULL anyway, you could alternatively use `calloc`... – Aconcagua Nov 24 '17 at 07:47
  • 1
    @Aconcagua In practice, yes, but technically, no: `calloc` allocates all-bits-zero and there are rare platforms where this is not a null pointer (imagine a platform with RAM or ROM mapped at all-bits-zero). – Gilles 'SO- stop being evil' Nov 24 '17 at 07:50
  • Thank you sir. People have been pointing out that cmpstr which was wrong but an easy fix but they did not realize I was struggling more on the memory aspect of it. – ZCode Nov 24 '17 at 15:38