tags:

views:

170

answers:

4

I'm trying to fill an array with names from a file:

Andrew
Andy
Bob
Dan
Derek
Joe
Pete
Richy
Steve
Tyler

Here is the function I wrote...but the program crashes when I run it:

#include <stdio.h>

main(){
  int i=0, size=10;
  char fname[15];
  char** data;
  char* name;
  FILE* fp;

  printf("Enter a filename to read names:\n");
  scanf("%s", fname);

  fp = fopen(fname, "r");
  if(fp == NULL)
    exit();

  data = (char**)malloc(sizeof(char**)*size);

  while(!feof(fp)){
    fscanf(fp, "%s", name);
    data[i] = (char*)malloc(sizeof(name));
    data[i] = name;
    i++;
  }

  fclose(fp);

  printf("\n\n");

  for(i=0; i<size; i++)
    printf("%s ", data[i]);

  free(data);
}

Anyone know what I'm doing wrong? Thanks

+4  A: 

You never allocate space for name. But you put stuff in on this line:

 fscanf(fp, "%s", name);

change

 char name[100];

and

fscanf(fp, "%99s", name);

you should limit the imput to filename too:

scanf("%14s", fname);

also you never free the data for each element in the array, if this were a sub-routine in a larger system it would have a memory leak.

Hogan
Oh...but how do I know how much space to allocate if I don't know how long the name is going to be? Do I have to scan characters on every line and count?
Andrew
you should have a line length limit; create a buffer and scan into it - the rest will be truncated. If you want an endlessly long lines possible, you should dynamically reallocate the buffer until everything is read. Then allocate exactly the number of bytes in the name (+1) and strncpy the name into it.
kibitzer
I alway just made a really big buffer. But you can scan first and count if you want. For a homework assignment you can just make a big buffer IMHO
Hogan
Ok, now the program doesn't crash...but when I print it prints Tyler Tyler Tyler...10 times.
Andrew
right you have to allocate space for name in data and do a strcpy not an assignment. see this answer: http://stackoverflow.com/questions/2241834/why-is-my-char-writable-and-sometimes-read-only-in-c/2241876#2241876
Hogan
@kibitzer good thing I don't program in C. This doesn't look like much more fun than counting 1s and 0s.
orokusaki
@Andrew: See my point 3 - that's why you're getting "Tyler Tyler Tyler"... The way you have it written, you're just setting EVERY data[i] to point to (the same) "name", which is going to be filled with "Tyler" at the end.
Reed Copsey
@orokusaki we are all glad you don't program in C too. :D
Hogan
@Hogan I only take high paying jobs.
orokusaki
+3  A: 

You have a couple of errors here:

1) You never allocate memory where name will get stored. You could get around this simply by using:

char name[128];

Then, when you use fscanf, you'd have:

fscanf(fp, "%127s", name); // stores this in name correctly, now...

2) You're inappropriately allocating space for data[i]:

data[i] = (char*)malloc(sizeof(name));

This will allocate space to hold one pointer to char (char*), since name is a char*. You need to be doing:

data[i] = (char*)malloc(sizeof(char) * (strlen(name) + 1 ) );

This will allocate enough space for the data plus a single terminating character.

3) You aren't assigning data[i] correctly. You can't just use = here, but need to use strcpy:

strcpy(data[i], name);

4) You're not freeing the individual pointers in the data[..] elements. You should add, after your printf, a free:

for(i=0; i<size; i++)
{
    printf("%s ", data[i]);
    free(data[i]);  // Free each pointer you allocate
}

Every malloc call should eventually have a matching free call.

Reed Copsey
Reed, I'm not going to downvote because you answered succintly and correctly, but you aren't helping him when you don't make him think about the process.
San Jacinto
Great thanks a lot, it is working now. And yes I now understand what I did wrong.
Andrew
@San Jacinto: I don't know - I felt like this was clear,but not just rewriting all of his code. Hard to help without pointing out why things are flaws... but I do see your point. @Andrew: I'm glad it's working, and you understand it now.
Reed Copsey
A: 

There are quite a few problems. Other answer talk about name not being initialized, so I won't repeat that.

A second problem is that you are not handling EOF correctly. feof will only return true once you've tried to read just past the end of the file. So you will get an empty 11'th name.

A final problem is that the data array can only hold 10 names. Because of the second problem you'll overflow the buffer with the blank 11'th name. Furthermore, your code depends on the details of the input file - give it a different input file with more names and you'll still crash even after fixing problem 2.

R Samuel Klatchko
A: 

The usual way to do this is to use a fixed-size buffer to initial read a line of data (hoping that it's large enough for all the data on the line), then allocate enough space for that item, and go on to the next one. If you want to get more elaborate, you can read with fgets, check whether the final character you read in was a new-line, and if not, read again and use-realloc to increase the size of the buffer, repeating as necessary until you run out of memory or see a new-line to let you know that you've read the entire line.

On another note, your loop:

while(!feof(fp)){
    fscanf(fp, "%s", name);
    data[i] = (char*)malloc(sizeof(name));
    data[i] = name;
    i++;
}

is broken -- it won't exit when it should. As it stands, your data will probably look like the last line of the file was read twice. As mentioned above, fgets is generally more suitable for this kind of task. With it, you'd generally want to write your loop something like:

while (fgets(name, sizeof(name), fp)) {
    data[i] = malloc(strlen(name));
    strcpy(data[i++], name);        
}

Not to sound like a proselytizing, but almost any other language will make it much easier to do a much better job of this. Just for example, in C++ you could use:

std::vector<std::string> data;
std::copy(std::istream_iterator<std::string>(infile),
          std::istream_iterator<std::string>(),
          std::back_inserter(data));

...and it would handle automatically resizing the individual strings and the array of strings automatically.

Jerry Coffin
I agree this would be easier in most other languages, however I feel that C is great for a first language because it gets you to really understand what your doing. C makes you use pointers for example, whereas Java pretty much hides them from you.
Andrew