1

A few weeks ago, I was using for first time (I'm not used to use them) floats, doubles, and I have some problems with the comparison operand. I also have had problems while trying to assign values to that types, but I've solved it as well...

Today, I'm making a library in C++, and I have found an error... well... strange? or just my stupid thinking?

This is the code:

ini::ini(const char * path, bool _autoflush_ /*= false*/) {
/* Storing file name ... */
f_name = new char[strlen(path)+1];
strcpy(f_name, path);

/* Storing autoflush ... */
autoflush = _autoflush_;

/* First step: getting file size */
    /* Open the file in read/append mode */
    FILE * fd = fopen(path, "r");
    /* On non-valid descriptor, goto next step directly */
    if(fd == NULL) f_size = 1; goto allocbuffer;

    /* Seek to the end */
    fseek(fd, 0, SEEK_END);
    /* Get file size */
    f_size = (unsigned long int)ftell(fd) + 1;

/* Second step: allocating memory for the buffer */ allocbuffer:
    cout << endl << endl << endl << endl << "Wanting " << sizeof(char)*f_size << " bytes of memory!" << endl << endl << endl << endl;
    /* Allocate buffer-space */
    buffer = (char*)malloc(sizeof(char)*f_size);
    if(buffer == NULL) {
        errord = (char*)malloc(strlen(INI_ERROR_NOT_ENOUGH_MEMORY) + 1);
        strcpy(errord, INI_ERROR_NOT_ENOUGH_MEMORY);
        cout << endl << endl << endl << endl << "Last error: \"" << errord << "\"." << endl << endl << endl << endl;
        return;
    }
    /* Initialize and fill it with null bytes */
    memset(buffer, 0, f_size);
    /* Goto next step */
    if(fd == NULL) goto endconstruct;

/* Third step: storing in the buffer */ loadbuffer:
    /* Rewind file descriptor */
    rewind(fd);
    /* Read from file */
    if(fread(buffer, 1, f_size, fd) != f_size) {
        errord = (char*)malloc(strlen(INI_ERROR_NOT_READED) + 1);
        strcpy(errord, INI_ERROR_NOT_READED);
        cout << endl << endl << endl << endl << "Last error: \"" << errord << "\"." << endl << endl << endl << endl;
        cout << endl << endl << endl << endl << "BYTES OF FILE: \"" << f_size << "\"." << endl << endl << endl << endl;
    }
    /* Close file descriptor */
    fclose(fd);
    /* Get number of lines */
    f_line = strnum(buffer, "\n") + 1;

/* End building of object */
endconstruct:
    /* Print out what is stored in the buffer NOW */
    cout << endl << endl << endl << endl << "Buffer is:" << endl << buffer << endl << endl << endl << endl;

return;

}

Probably ini libraries are already created, and much better than my. But I'm starting learning C++ from C, and I want to practice with something interesting and useful. I missed the class declaration, I don't know if it's necessary to paste it here, but here is:

    /** @def INI_ERROR_NOT_READED
    @brief <em>Not readed as many bytes as required</em>
*/
#define INI_ERROR_NOT_READED "Not readed as many bytes as required"
/** @def INI_ERROR_NOT_ENOUGH_MEMORY
    @brief <em>There is not enough memory</em>
*/
#define INI_ERROR_NOT_ENOUGH_MEMORY "There is not enough memory"

/** @class ini
    @brief Class to describe <em>ini</em> files.

    It describes an ini file. All the file is readed and loaded
    in memory, for faster access. This class is the
    improved & C++ version of the old, monstruous
    functions defined in the old, monstruous IO Utilities
    Library. Writting functions use dynamic memory reallocation
    and file flush to the filesystem.
*/
class ini {
    public:
        /** @brief Constructor. Gives initial memory for the buffer and loads all the file in that buffer.
         * 
         * @param path - Path of the <em>ini</em> file to open.
         * @param _autoflush_ - Whether to auto-flush changes to hard disk or not.
         *  If you don't set it to any value, <em>false</em> is taked as default
         *  value and you have to flush changes manually using member function flush().
         *  Setting it to <em>true</em> may make it less efficient, so be careful
         *  if you're going to make a lot of changes in the <em>ini</em> file.
        */
        ini                     (const char * path, bool _autoflush_ = false);
        /** @brief Destructor. Frees the memory pointed by <em>buffer</em> and destroys the #ini object.
         * 
         * It's very important to free the memory buffer, to avoid memory corruptions.
        */
        ~ini                    (void);
        /** @brief Gets last error stored in private member <em>errord</em>.
         * 
         * @return Last error-descriptor as string.
        */
        char *      geterror    (void);
        /** @brief Flush changes made in the buffer to the hard disk.
         * 
         * You can do it manually or set auto-flushing by the second argument of
         * ini::ini().
         * 
         * @par Example of usage:
         * @code
         *  ini inid("myini.ini");
         *  // make changes
         *  inid.flush();
         * @endcode
        */
        void        flush       (void);
        /** @brief Flush changes made in the buffer to *another* file the hard disk.
         * 
         * Using this function instead of normal flush(void), you are able to
         * save the buffer to another #ini file that is not the original one.
         * 
         * @par Example of usage:
         * @code
         *  ini inid("myini.ini");
             *  // make changes
         *  inid.flush("myini.backup.ini");
         * @endcode
        */
        void        flush       (const char * path);
        /** @brief Checks if a section exists.
         * 
         * @param tsection - The name of the section to check, without the braces.
         * 
         * @return #true if the section exists; #false if not.
        */
        bool        sectExists  (const char * tsection);
        /** @brief Gets the line in that a section starts.
         * 
         * @param tsection - The name of the section to check, without the braces.
         * 
         * @return The line in that the section starts; -1 if not-founded section.
         *  Keep in mind that the first line is 1, the second, 2,...
        */
        int         sectStart   (const char * tsection);
        /** @brief Gets the line in that a section ends.
         * 
         * @param tsection - The name of the section to check, without the braces.
         * 
         * @return The line in that the section ends; -1 if not-founded section.
         *  Keep in mind that the first line is 1, the second, 2,...
        */
        int         sectStop    (const char * tsection);
        /** @brief Checks if a key exists.
         * 
         * @param tsection - The name of the section to check, without the braces.
         *  If the key is outside any section (if it's a #KWOS), then <em>tsection</em>
         *  should be #KWOS.
         * @param tkey - The name of the key to check.
         * 
         * @return #true if the key exists in the specified section; #false if not.
        */
        int         keyExists   (const char * tsection, const char * tkey);
        /** @brief Reads the value of a key as a string.
         * 
         * @param tsection - The name of the section to read from, without the braces.
         *  If the key is outside any section (if it's a #KWOS), then <em>tsection</em>
         *  should be #KWOS.
         * @param tkey - The name of the key to read its value.
         * @param tval - The default string to return if cannot found the key.
         * 
         * @return The value of the key <em>tkey</em> in section <em>tsection</em>; or
         *  <em>tval</em> when non-existing key.
        */
        char *      read        (const char * tsection, const char * tkey, const char * tval);
        /** @brief Reads the value of a key as an integer value.
         * 
         * @param tsection - The name of the section to read from, without the braces.
         *  If the key is outside any section (if it's a #KWOS), then <em>tsection</em>
         *  should be #KWOS.
         * @param tkey - The name of the key to read its value.
         * @param tval - The default value to return if cannot found the key.
         * 
         * @return The value of the key <em>tkey</em> in section <em>tsection</em>; or
         *  <em>tval</em> when non-existing key.
        */
        long int    readi       (const char * tsection, const char * tkey, int tval);
        bool        delKey      (const char * tsection, const char * tkey);
        bool        delSect     (const char * tsection);
        bool        write       (const char * tsection, const char * tkey, const char * tval);
        bool        write       (const char * tsection, const char * tkey, int tval);
    private:
        unsigned long int       f_size; /**< File size. */
        unsigned int            f_line; /**< Number of lines of the <em>ini</em> file. */
        char *                  buffer; /**< Memory buffer to store data. Dynamimcally reallocated. */
        char *                  f_name; /**< File name. */
        bool                 autoflush; /**< Whether to auto-flush to hard disk or not. */
        char *                  errord; /**< Last error stored internally by the functions of the #ini class. */
};

After a few 'tests', I have finally discovered that the problem is in the 'f_size' variable. Why? Don't know. But, if I print it out to stdout, it displays a very very big number. That's the problem of the memory-assignment (with malloc) error and the subsequent error at reading from file (or initialization with memset).

Help is much appreciated. And links, refs or explanations to have me see my error, and continue learning.

Thanks!

P.S.: I'm using g++ in a Linux Debian "squeeze", amd64.

shura-astrozero
  • 165
  • 2
  • 10
  • 2
    BTW `strlen` and `new char[]`? Even `malloc`? You're still thinking in C, use `std::string` and prosper – Kos Dec 25 '11 at 17:39
  • But so much functions I've seen 'still' use _const char*_ and _char*_... and I need _realloc_ for dynamically reallocate memory for the buffer. – shura-astrozero Dec 25 '11 at 17:40
  • 1
    Why would you need a dynamically reallocated memory for a buffer if you're not writing a container class? – Kos Dec 25 '11 at 17:42
  • 1
    `ftell` returns a _signed_ long int. Your conversion is potentially dramatic since `ftell` returns `-1` on error. The `+1` is also suspicious on that line. The `goto allocbuffer;` should really, really be avoided in C++. It's okay-ish in C (not sure it is in that circumstance though), but it is _much_ harder to get right in C++. (So you're still thinking too much in terms of C - narrow down your problem _a lot_ with a much simpler piece of code, and you'll probably get better advice.) – Mat Dec 25 '11 at 17:45
  • Because the _ini_ files are loaded in memory and the user may want write to them. So, if the memory pointed by _buffer_ is assigned by operator _new_, I won't be able to change dynamically the size of memory. I really need _realloc_. About 'std::string' class... well, I'm not very keen on it... – shura-astrozero Dec 25 '11 at 17:47
  • If you're using labels you should start a line with the label. I found it incredibly hard to find the labels since you've comments in front of them, pushing them to the far right. – DarkDust Dec 25 '11 at 17:47
  • 1
    You do not need realloc if you're using `std::string` and `std::ifstream`. I recommend you learn to use those as it will make your memory management much easier. – DarkDust Dec 25 '11 at 17:49
  • @Mat, thanks... and the **goto**... I didn't use to use it, but... in that situation, I thought it's more comfortable. I will change it. – shura-astrozero Dec 25 '11 at 17:49
  • @DarkDust I've just opened cplusplus.com to searh for :D – shura-astrozero Dec 25 '11 at 17:50

3 Answers3

3

Here is one subtle but significant issue:

 if(fd == NULL) f_size = 1; goto allocbuffer;

If the file does exist, the logic still jumps to label allocbuffer.

To fix this, use braces. And indent for crystal clarity.

 if(fd == NULL)
 {
      f_size = 1;
      goto allocbuffer;
 }

By skipping to the exit, variables are not initialized properly.

wallyk
  • 53,902
  • 14
  • 79
  • 135
2

In addition to the issue pointed out by wallyk, whenftell encounters an error it returns -1 and casting that to an unsigned long is causing an overflow.

In any case, don't use ftell to get a file's size. Use fstat or similar.

Frederick Cheung
  • 79,397
  • 6
  • 137
  • 164
0

I won't debug the code for you (this more belongs to codereview.se), but here are some hints:

f_name = new char[strlen(path)+1];
strcpy(f_name, path);

Instead: define f_name as std::string and do a...

f_name = path;

        errord = (char*)malloc(strlen(INI_ERROR_NOT_READED) + 1);
        strcpy(errord, INI_ERROR_NOT_READED);
        cout << "Last error: \"" << errord << "\"." << endl;

Are you, like, dynamically allocating memory just to write a string into the screen? Instead do simply:

        errord = INI_ERROR_NOT_READED;
        cout << "Last error: \"" << errord << "\"." << endl;

Also to read a whole file into a buffer, there are simpler ways (as in, a few lines of code). See https://stackoverflow.com/a/2602060/399317 .


Long story short: new[] and malloc aren't your friends unless you're writing your own containers. Use STL containers instead (std::vector for instance, std::string is helpful too).

Community
  • 1
  • 1
Kos
  • 64,918
  • 23
  • 156
  • 223
  • Thank you! So, I will use std::string and... @Kos... 'direct' assignment of 'errord'? But, then I will got a seg fault or something like that. Pointers are source of many of my problems in the past... and 'errord' is a private member of class 'ini'. 'cout' in that situation is just for print out before the seg fault I got... – shura-astrozero Dec 25 '11 at 17:55