tags:

views:

226

answers:

5

Hi,

I have a structure with the following definition:

typedef struct myStruct{
    int a;
    char* c;
    int f;
} OBJECT;

I am able to populate this object and write it to a file. However I am not able to read the char* c value in it...while trying to read it, it gives me a segmentation fault error. Is there anything wrong with my code:

//writensave.c

#include "mystruct.h"
#include <stdio.h>
#include <string.h>


#define p(x) printf(x)

int main()
{
    p("Creating file to write...\n");
    FILE* file = fopen("struct.dat", "w");
    if(file == NULL)
    {
        printf("Error opening file\n");
        return -1;
    }

    p("creating structure\n");
    OBJECT* myObj = (OBJECT*)malloc(sizeof(OBJECT));
    myObj->a = 20;
    myObj->f = 45;
    myObj->c = (char*)calloc(30, sizeof(char));
    strcpy(myObj->c, 
        "This is a test");
    p("Writing object to file...\n");
    fwrite(myObj, sizeof(OBJECT), 1, file);
    p("Close file\n");
    fclose(file);
    p("End of program\n");
    return 0;       
}

Here is how I am trying to read it:

//readnprint.c
#include "mystruct.h"
#include <stdio.h>
#define p(x) printf(x)
int main()
{   
    FILE* file = fopen("struct.dat", "r");
    char* buffer;
    buffer = (char*) malloc(sizeof(OBJECT));
    if(file == NULL)
    {
        p("Error opening file");
        return -1;
    }

    fread((void *)buffer, sizeof(OBJECT), 1, file);
    OBJECT* obj = (OBJECT*)buffer;
    printf("obj->a = %d\nobj->f = %d \nobj->c = %s",
        obj->a,
        obj->f,
        obj->c);
    fclose(file);
    return 0;
}
+1  A: 

You're saving a pointer to a char, not the string itself. When you try to reload the file you're running in a new process with a different address space and that pointer is no longer valid. You need to save the string by value instead.

Stu Mackellar
+2  A: 

When you write your object, you're writing the pointer value to the file instead of the pointed-to information.

What you need to do is not just fwrite/fread your whole structure, but rather do it a field at a time. fwrite the a and the f as you're doing with the object, but then you need to do something special with the string. Try fwrite/fread of the length (not represented in your data structure, that's fine) and then fwrite/fread the character buffer. On read you'll need to allocate that, of course.

dash-tom-bang
+2  A: 

Your first code sample seems to assume that the strings are going to be no larger than 30 characters. If this is the case, then the easiest fix is probably to re-define your structure like this:

typedef struct myStruct{
    int a;
    char c[30];
    int f;
} OBJECT;

Otherwise, you're just storing a pointer to dynamically-allocated memory that will be destroyed when your program exits (so when you retrieve this pointer later, the address is worthless and most likely illegal to access).

bta
What about structure alignment?
Learner
What about structure alignment? As long as the data is read by the same application that wrote it, it should be a non issue. Any padding will be uninitialized data when written and may change when reading, but you're not counting on it being in any specific state in the first place...
dash-tom-bang
If you're concerned about structure alignment, your compiler most likely has an option to "pack" your structures (that is, use no padding bytes). In gcc, you would add `__attribute__((__packed__))` after the closing brace of the `struct` definition. However as dash-tom-bang pointed out, alignment should not be an issue if you are reading and writing the data from the same application.
bta
+1  A: 

I would like to add a note about a potential portability issue, which may or may not exist depending upon the planned use of the data file.

If the data file is to be shared between computers of different endian-ness, you will need to configure file-to-host and host-to-file converters for non-char types (int, short, long, long long, ...). Furthermore, it could be prudent to use the types from stdint.h (int16_t, int32_t, ...) instead to guarantee the size you want.

However, if the data file will not be moving around anywhere, then ignore these two points.

Sparky
A: 

The char * field of your structure is known as a variable length field. When you write this field, you will need a method for determining the length of the text. Two popular methods are:
1. Writing Size First
2. Writing terminal character

Writing Size First
In this method, the size of the text data is written first, followed immediately by the data.
Advantages: Text can load quicker by block reads.
Disadvantages: Two reads required, extra space required for the length data.
Example code fragment:

struct My_Struct
{
   char * text_field;
};

void Write_Text_Field(struct My_Struct * p_struct, FILE * output)
{
  size_t text_length = strlen(p_struct->text_field);
  fprintf(output, "%d\n", text_length);
  fprintf(output, "%s", p_struct->text_field);
  return;
}

void Read_Text_Field(struct My_STruct * p_struct, FILE * input)
{
  size_t text_length = 0;
  char * p_text = NULL;
  fscanf(input, "%d", &text_length);
  p_text = (char *) malloc(text_length + sizeof('\0'));
  if (p_text)
  {
     fread(p_text, 1, text_length, input);
     p_text[text_length] = '\0';
  }
}

Writing terminal character In this method the text data is written followed by a "terminal" character. Very similar to a C language string. Advantages: Requires less space than Size First.
Disadvantages: Text must be read one byte at a time so terminal character is not missed.

Fixed size field
Instead of using a char* as a member, use a char [N], where N is the maximum size of the field. Advantages: Fixed sized records can be read as blocks. Makes random access in files easier. Disadvantages: Waste of space if all the field space is not used. Problems when the field size is too small.

When writing data structures to a file, you should consider using a database. There are small ones such as SQLite and bigger ones such as MySQL. Don't waste time writing and debugging permanent storage routines for your data when they have already been written and tested.

Thomas Matthews
I'd like to see some code wrt the first two methods you've mentioned...
deostroll
@deostroll: I added code for the first method. The second method can use standard library functions for string I/O.
Thomas Matthews
like u had mentioned earlier I have kept a field which stores the length of the string (to obj->a)...I've written all of these to the file. But I still keep getting segment fault while trying to read > http://pastebin.com/UuDGVQxZ
deostroll