0

I have built a book struct that looks like this:

typedef struct _book{
    char name[NAME_LENGTH];
    char authors[AUTHORS_NAME_LENGTH];
    char publisher[PUBLISHER_NAME_LENGTH];
    char genre[GENRE_LENGTH];
    int year;
    int num_pages;
    int copies;
}book;

i'm trying to define a library which is an array of books, so that later on i could deposit books in the library with another function. had problems with memory write/read when defined the library like this library[BOOK_NUM], so i decided to allocate.

the thing is, it only lets my allocate inside the main function. when i write this line:

book *library = (book*)malloc(BOOK_NUM*sizeof(book));

outside the main() it gives me an error:

IntelliSense: function call is not allowed in a constant expression

error C2099: initializer is not a constant

but if i move the above line to be inside main() it works. why is that?

also, what is the better way to define the array so that i could change it later with other functions?

SKD
  • 464
  • 1
  • 4
  • 16
Rtucan
  • 157
  • 1
  • 11

2 Answers2

6

You might declare a global or static variable, assuming BOOK_NUM is some #define-d constant (e.g. #define BOOK_NUM 100 somewhere before in your code):

 book library[BOOK_NUM];

However, heap allocation is generally preferable, because the resource usage is limited at runtime, not at compile-time or start of execution time.

If BOOK_NUM was extremely big (eg a billion) you could have an issue (program won't be runnable because of lack of memory).

If BOOK_NUM was slightly small (e.g. a dozen) you could have an issue in running some cases (not enough space for books).

If you (wrongly!) declared book library[BOOK_NUM]; as some local variable (e.g. in main), the call frame should be small enough (because the entire call stack is limited to a few mega-bytes, so individual call frames should not exceed a few kilobytes) so BOOK_NUM should be kept small (a few dozens at most).

To quote the GNU coding standards:

4.2 Writing Robust Programs

Avoid arbitrary limits on the length or number of any data structure, including file names, lines, files, and symbols, by allocating all data structures dynamically

So a better way could be to have:

typedef struct book_st {
  char* name;
  char* authors;
  char* publisher;
  char* genre;
  int year;
  int num_pages;
  int copies;
} book;

then a "making function" (or "constructing" function) like

/* returns a freshly allocated book to be deleted by delete_book;
   the strings arguments should be not null and are duplicated. */
book* make_book(const char*n, const char*a, const char*p, 
                const char*g, int y, int np, int c) {
  assert (n != NULL);
  assert (a != NULL);
  assert (p != NULL);
  assert (g != NULL);
  book* b = malloc(sizeof(book));
  if (!b) { perror("malloc book"); exit(EXIT_FAILURE); };
  memset (b, 0, sizeof(book)); // useless, but safe
  char* pname = strdup(n);
  if (!pname) { perror("strdup name"); exit(EXIT_FAILURE); };
  char* pauth = strdup(a);
  if (!pauth) { perror("strdup author"); exit(EXIT_FAILURE); };
  char *ppub = strdup(p);
  if (!ppub) { perror("strdup publisher"); exit(EXIT_FAILURE); };
  char *pgenre = strdup(g);
  if (!pgenre) { perror("strdup genre"); exit(EXIT_FAILURE); };
  b->name = pname;
  b->authors = pauth;
  b->publishers = ppub;
  b->genre = pgenre;
  b->year = y;
  b->num_pages = np;
  b->copies = c;
  return b;
}

Notice that every call to malloc should be tested, because malloc could fail. Here I just exit with some error message; in some cases you would want to recover from malloc failure (e.g. a server might want to continue processing future requests), but that is boringly difficult (you might need to free any unseless malloc-ed pointer so far, etc...).

Of course, you need a destroying or deleting function to release memory, like:

/* destroy and free a book obtained by make_book */
void delete_book(book*b) {
  if (!b) return;
  free (b->name), b->name = NULL;
  free (b->authors), b->authors = NULL;
  free (b->publisher), b->publisher = NULL;
  free (b->genre), b->genre = NULL;
  free (b);
}

Notice my defensive programming style. I am clearing the malloc-ed book pointer before filling it; I am setting to NULL every pointer field in book just after free-ing it. In principle both are useless.

BTW, you could make your library a struct ending with a flexible array member:

struct library_st {
   unsigned size; // allocate size
   unsigned nbbooks; // actual number of books
   book* books[]; // actually, size slots
};

and have functions like struct library_st*make_library(unsigned s); and struct library_st*add_book(struct library_st*lib, book*book); which would return perhaps an updated and reallocated library.

The main thing in C is to document the memory allocation discipline. Every function should say (at least in a comment) who is in charge of freeing pointers and how.

Read much more (at least for concepts and terminology) about virtual address space, C dynamic memory allocation, memory leaks, garbage collection. Notice that reference counting is not a silver bullet.

Consider using Linux as your primary development environment on your laptop. It has good tools (gcc -Wall -g -fsanitize=address with a recent GCC, gdb, valgrind, Boehm's conservative GC ...) and lots of free software whose source code is worth studying to learn more about C programming.

BTW, to store your library on the disk, consider serialization techniques (and textual formats à la JSON), or perhaps sqlite or some real database (PostGreSQL, MongoDB, ...)

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • #define BOOK_NUM 50. i did encounter problems when tried to define like this : book library[BOOK_NUM]; thats why i tried to allocate. but had also problems with allocation when the malloc was outside of main() – Rtucan Jan 14 '16 at 08:50
  • when i defined book library[BOOK_NUM]; it gave me some problems with other functions when i tried to put values in the array – Rtucan Jan 14 '16 at 08:53
  • 1
    rtucan, defining a global array of books is just fine (it may even be a reasonable design decision). It should just work. Which "problems with other functions" did you encounter? – Peter - Reinstate Monica Jan 14 '16 at 09:06
2

You can only call malloc inside a function. main () is a function. You can write other functions. You can't just declare a global variable and initialise it by calling a function.

gnasher729
  • 51,477
  • 5
  • 75
  • 98