tags:

views:

199

answers:

5

I've mulled over this for at least an hour and I still can't find out what the problem is.

#include <stdio.h>

typedef struct
{
    int Level;
    char* Name;
} Base;

Base baseStruct;

int main(int argc, char *argv[])
{
    scanf("%s", baseStruct.Name);
    scanf("%d", &baseStruct.Level);
    printf("%s :: Level %d\n", baseStruct.Name, baseStruct.Level);
    return 0;
}

What happens is, I go and enter the "Name" string, then when I type and enter the integer the program crashes. What is going on?

+12  A: 
scanf("%s", ...)

This expects a buffer (scanf needs to write into it) and you give it an uninitialized pointer, that could point anywhere.

Consider doing one of the following:

  1. Make Name a character buffer instead:

    typedef struct
    {
        int Level;
        char Name[100];
    } Base;
    
  2. Initialize it from the heap:

    baseStruct.Name = malloc(100); /* do not forget to cleanup with `free()` */
    

You should also specify max string length in scanf format string to prevent overflow:

/* assume 'Name' is a buffer 100 characters long */
scanf("%99s", baseStruct.Name);
Alex B
@Alex B: Max string length is optional from the `scanf` point of view, but in order to ensure that you will get a working program it should be treated as mandatory. Not including the max length specifier will create yet another program vulnerable to buffer overflows.
Schedler
@Schedler I'm not sure what you mean. That's exactly what I said in my answer, no?
Alex B
+1  A: 

Name is just an uninitialized pointer to a string. It doesn't point to anything useful. You'll need to initialize it properly to a string buffer. Also, you may want to limit the string through the formatting (like %100s) to make sure you don't overrun your buffer.

EboMike
Where do you find a version of the STL for C?
Sinan Ünür
Well, sue me, I missed me tag that this is a C-specific problem.
EboMike
+1  A: 

Don't feel bad everyone makes that mistake. The char * stands for a "pointer to a character" but the memory for the string itself is not allocated.

Add:

baseStruct.Name = malloc(sizeof(char)*100);

(note my syntax may be off a little)

Gabriel
Of course, this will spectacularly crash if the user enters more than 100 characters. Using scanf on strings is unsafe unless you're absolutely sure about the input.
EboMike
Also, you'll need to cast the result to char *.
EboMike
@EboMike This is C. So, no need to cast the return value of `malloc`. @Gabriel `sizeof(char)` is always `1`.
Sinan Ünür
A: 

You haven't allocated any storage for Base.Name. You're scanning a string into a a pointer that doesn't point to any storage.

Allocate some space for the string. The problem is that you don't know how big a string will be copied in using scanf. Suppose you malloc 256 bytes and then scanf loads in a 300 byte string? Either allocate a string sufficiently large to handle all possible results from scanf, or modify your scanf to limit the characters, like:

baseStruct.Name = malloc(sizeof(char) * 256);
scanf("%256s", baseStruct.Name);
Kluge
The standard says `sizeof(char)` is always 1, so it's not really necessary.
Alex B
**Ouch!** You allocated 256 bytes and specified `%256s`.
Sinan Ünür
`sizeof(char)` is always 1, so it is redundant. Alternatively, you *could* use `sizeof(*baseStruct.Name)` if it is likely that the type of `baseStruct.Name` could change from `char*` to `wchar_t*`, etc.
dreamlax
@Kludge: I think allocating a string sufficiently large is not a realistic option, as the size of this string would likely be exactly the maximum size `malloc` can return under, leaving the program still vulnerable to buffer overflows.
Schedler
@Sinan: Ouch is right. Should have been %255s. @dreamlax: Also right. Sigh...
Kluge
A: 

As others have pointed out, baseStruct.Name does not point to a valid memory region. However, allocating a fixed sized buffer is no safer. For a learning exercise, use

typedef struct
{
    int Level;
    char Name[1];
} Base;

and enter long strings to examine effects of buffer overflows.

For safe handling of input of indeterminate length, use fgets and sscanf or strtol (or strtoul if Base.Level cannot be negative.

Here is an example:

#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define INITIAL_BUFSIZE 100
#define MAX_BUFSIZE 30000

char *myreadline(FILE *fin) {
    char *buffer;
    int offset = 0;
    int bufsize = INITIAL_BUFSIZE;
    buffer = malloc(bufsize);

    if ( !buffer ) {
        return NULL;
    }

    while ( fgets(buffer + offset, bufsize, fin) ) {
        size_t last = strlen(buffer) - 1;
        if ( buffer[last] == (char) '\n' ) {
            buffer[last] = 0;
            break;
        }
        else {
            char *tmp;
            offset += bufsize - 1;
            bufsize *= 2;
            if ( bufsize > MAX_BUFSIZE ) {
                break;
            }
            tmp = realloc(buffer, bufsize);
            if ( !tmp ) {
                break;
            }
            else {
                buffer = tmp;
            }
        }
    }
    return buffer;
}

int myreadint(FILE *fin, int *i) {
    long x;
    char *endp;
    char *line = myreadline(fin);

    if ( !line ) {
        return 0;
    }

    x = strtol(line, &endp, 10);

    if ( (!*endp || isspace((unsigned char) *endp) )
            && (x >= INT_MIN) && (x <= INT_MAX ) ) {
        *i = (int) x;
        free(line);
        return 1;
    }

    return 0;
}

typedef struct base_struct {
    int Level;
    char* Name;
} Base;

int main(int argc, char *argv[]) {
    Base bs;
    int i;

    puts("Enter name:");
    bs.Name = myreadline(stdin);
    if ( !bs.Name ) {
        fputs("Cannot read Name", stderr);
        return EXIT_FAILURE;
    }

    puts("Enter level:");
    if ( myreadint(stdin, &i) ) {
        bs.Level = i;
        printf("Name: %s\nLevel: %d\n", bs.Name, bs.Level);
        free(bs.Name);
    }
    else {
        fputs("Cannot read Level", stderr);
        return EXIT_FAILURE;
    }


    return 0;
}

Output:

C:\Temp> t
Enter name:
A dark and mysterious dungeon
Enter level:
3456772
Name: A dark and mysterious dungeon
Level: 3456772
Sinan Ünür
I don't think just using `sscanf` instead of `scanf` will make any difference. You still have to put max string length in the format specifier if using either of them.
Alex B
Using `fgets` to read input allows you to adjust the buffer size to accommodate any practical size string by using `realloc`. You can then use `sscanf` or `strto(ld)` to convert numbers. Obviously, you would not use `sscanf` when you are just reading a string.
Sinan Ünür
@Sinan OK I see what you mean.
Alex B