1

I want to implement a searching table and here's the data:

20130610    Diamond CoinMate    11.7246 15.7762 2897
20130412    Diamond Bithumb     0.209   0.2293  6128
20130610    OKCash  Bithumb     0.183   0.2345  2096
20130412    Ethereum    Chbtc   331.7282    401.486 136786
20170610    OKCash  Tidex       0.0459  0.0519  66
...

and my code

typedef struct data{
    int *date;
    string currency[100];
    string exchange[100];
    double *low;
    double *high;
    int *daily_cap;
} Data;

int main()
{
    FILE *fp = fopen("test_data.txt", "r");
    Data tmp[50];
    int i = 0;
    while (!feof(fp)){
        fscanf(fp, "%d%s%s%f%f%7d", &tmp[i].date, tmp[i].currency, tmp[i].exchange, &tmp[i].low, &tmp[i].high, &tmp[i].daily_cap);
        i++;
    }
    fclose(fp);
}

but the first problem is that I can't create a large array to store my struct like

Data tmp[1000000]

and even I try just 50 elements , the program break down when finish main(). can anyone tell how to fix it or give me a better method, thanks.

Samantha
  • 975
  • 6
  • 20
Sam
  • 101
  • 5
  • Large data structures shall **never** be created as local variables (i.e. automatic storage duration). Always use dynamic allocation for large data structures, i.e. `malloc` and friends. Creating large data structures as local variables will cause stack overflow and program failure. – Support Ukraine Apr 02 '18 at 07:31

3 Answers3

3

You can not scan a value to an unallocated space, in other words, you need room for all those pointers in the struct, switch to

typedef struct data{
    int date;
    string currency[100];
    string exchange[100];
    double low;
    double high;
    int daily_cap;
} Data;

Or use malloc to assign space to those pointers before using them.

while (!feof(fp)){
   tmp[i].date = malloc(sizeof(int));
   ...

But in this case, you don't need to pass the address of such members to fscanf since they are already pointers:

fscanf(fp, "%d%s%s%f%f%7d", &tmp[i].date, ..

should be

fscanf(fp, "%d%s%s%lf%lf%7d", tmp[i].date, ...

Notice that double wants %lf instead of %f

This is also very confusing:

typedef struct data{
    int *date;
    string currency[100];
    ...

Is string a typedef of char? I think you mean string currency; since string is usually an alias of char *, in this case you need room for this member too: currency = malloc(100);

Finally, take a look to Why is “while ( !feof (file) )” always wrong?

There are too many errors in a short snippet, I suggest you to read a good C book.

Your code corrected using dynamic memory that allows you to reserve space for a big amount of data (see the other answer of @LuisColorado) and using fgets and sscanf instead of fscanf:

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

typedef struct data{
    int date;
    char currency[100];
    char exchange[100];
    double low;
    double high;
    int daily_cap;
} Data;

int main(void)
{
    FILE *fp = fopen("test_data.txt", "r");
    /* Always check the result of fopen */
    if (fp == NULL) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }

    Data *tmp;
    tmp = malloc(sizeof(*tmp) * 50);
    if (tmp == NULL) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }

    char buf[512];
    int i = 0;
    /* Check that you don't read more than 50 lines */
    while ((i < 50) && (fgets(buf, sizeof buf, fp))) {
        sscanf(buf, "%d%99s%99s%lf%lf%7d", &tmp[i].date, tmp[i].currency, tmp[i].exchange, &tmp[i].low, &tmp[i].high, &tmp[i].daily_cap);
        i++;
    }
    fclose(fp);
    /* Always clean what you use */
    free(tmp);
    return 0;
}
David Ranieri
  • 39,972
  • 7
  • 52
  • 94
2

Of course you can't. Think you are creating an array of 1.0E6 registers of sizeof (Data) which I guess is not less than 32 (four pointers) and 200 bytes (not less than this, as you don't give the definition of type string) and this is 232MBytes (at least) in a 64 byte machine (in 32bit it is 216MBytes) and that in case the type string is only one character wide (what I fear is not) In case string is a typedef of char * then you have 432 pointers in your struct giving to 432MBytes in only one variable. Next, if you are declaring this absolutely huge variable as a local variable, you must know that te stack in most unix operating systems is limited to around 8Mb, and this means you need to build your program with special parameters to allow a larger stack max size. And also you probably need your account to raise to that size also the ulimits to make the kernel to allow you such a large stack size segment.

Please, next time, give us full information, as not knowing the definition of the string type, or posting an incomplete program, only allows us to make guesses on what can be ongoing, and not to be able to discover actual errors. This makes you to waste your time, and for us the same. Thanks.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31
2

If your list of currency and exchange are known before hand, then there is no need to allocate or store any arrays within your struct. The lists can be global arrays of pointers to string literals and all you need do is store a pointer to the literal for both currency and exchange (you can even save a few more bytes by storing the index instead of a pointer).

For example, your lists of exchanges can be stored once as follows:

const char *currency[] = { "Diamond", "OKCash", "Ethereum" },
           *exchange[] = { "CoinMate", "Bithumb", "Chbtc", "Tidex" };

(if the number warrants, allocate storage for the strings and read them from a file)

Now you have all of the possible strings for currency and exchange stored, all you need in your data struct is a pointer for each, e.g.

 typedef struct {
    const char *currency, *exchange;
    double low, high;
    unsigned date, daily_cap;
} data_t;

(unsigned gives a better range and there are no negative dates or daily_cap)

Now simply declare an array of data_t (or allocate for them, depending on number). Below is a simply array of automatic storage for example purposes. E.g.

#define MAXD 128
...
    data_t data[MAXD] = {{ .currency = NULL }};

Since you are reading 'lines' of data, fgets or POSIX getline are the line-oriented choices. After reading a line, you can parse the line with sscanf using temporary values, compare whether the values for currency and exchange read from the file match values stored, and then assign a pointer to the appropriate string to your struct, e.g.

int main (void) {

    char buf[MAXC] = "";
    size_t n = 0;
    data_t data[MAXD] = {{ .currency = NULL }};

    while (n < MAXD && fgets (buf, MAXC, stdin)) {
        char curr[MAXE] = "", exch[MAXE] = "";
        int havecurr = 0, haveexch = 0;
        data_t tmp = { .currency = NULL };
        if (sscanf (buf, "%u %31s %31s %lf %lf %u", &tmp.date, 
                    curr, exch, &tmp.low, &tmp.high, &tmp.daily_cap) == 6) {
            for (int i = 0; i < NELEM(currency); i++) {
                if (strcmp (currency[i], curr) == 0) {
                    tmp.currency = currency[i];
                    havecurr = 1;
                    break;   
                }
            }
            for (int i = 0; i < NELEM(exchange); i++) {
                if (strcmp (exchange[i], exch) == 0) {
                    tmp.exchange = exchange[i];
                    haveexch = 1;
                    break;
                }
            }
            if (havecurr & haveexch)
                data[n++] = tmp;
        }
    }
    ...

Putting it altogether in a short example, you could do something similar to the following:

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

#define MAXC 256
#define MAXD 128
#define MAXE  32

#define NELEM(x) (int)(sizeof (x)/sizeof (*x))

const char *currency[] = { "Diamond", "OKCash", "Ethereum" },
           *exchange[] = { "CoinMate", "Bithumb", "Chbtc", "Tidex" };

typedef struct {
    const char *currency, *exchange;
    double low, high;
    unsigned date, daily_cap;
} data_t;

int main (void) {

    char buf[MAXC] = "";
    size_t n = 0;
    data_t data[MAXD] = {{ .currency = NULL }};

    while (n < MAXD && fgets (buf, MAXC, stdin)) {
        char curr[MAXE] = "", exch[MAXE] = "";
        int havecurr = 0, haveexch = 0;
        data_t tmp = { .currency = NULL };
        if (sscanf (buf, "%u %31s %31s %lf %lf %u", &tmp.date, 
                    curr, exch, &tmp.low, &tmp.high, &tmp.daily_cap) == 6) {
            for (int i = 0; i < NELEM(currency); i++) {
                if (strcmp (currency[i], curr) == 0) {
                    tmp.currency = currency[i];
                    havecurr = 1;
                    break;   
                }
            }
            for (int i = 0; i < NELEM(exchange); i++) {
                if (strcmp (exchange[i], exch) == 0) {
                    tmp.exchange = exchange[i];
                    haveexch = 1;
                    break;
                }
            }
            if (havecurr & haveexch)
                data[n++] = tmp;
        }
    }

    for (size_t i = 0; i < n; i++)
        printf ("%u  %-10s  %-10s  %8.4f  %8.4f  %6u\n", data[i].date,
                data[i].currency, data[i].exchange, data[i].low,
                data[i].high, data[i].daily_cap);
}

Example Use/Output

$ ./bin/coinread <dat/coin.txt
20130610  Diamond     CoinMate     11.7246   15.7762    2897
20130412  Diamond     Bithumb       0.2090    0.2293    6128
20130610  OKCash      Bithumb       0.1830    0.2345    2096
20130412  Ethereum    Chbtc       331.7282  401.4860  136786
20170610  OKCash      Tidex         0.0459    0.0519      66

With this approach, regardless whether you allocate for your array of struct or use automatic storage, you minimize the size of the data stored by not duplicating storage of known values. On x86_64, your data_t struct size will be approximately 40-bytes. With on average a 1-4 Megabyte stack, you can store a lot of 40-byte structs safely before you need to start allocating. You can always start with automatic storage, and if you reach some percentage of the available stack space, dynamically allocate, memcpy, set a flag to indicate the storage in use and keep going...

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85