Here is an update to my previous thread which you can find here. Again, I'd very much appreciate any advice or comments about the structure/logic of the program, style of the code, or reusability of the features.The fread_twelve function has been heavily modified to deal with detecting and reading the last entry, which is padded from 12 to 16 bits. Let me know what you think!I'm also quite eager to hear how I can balance the heap. Valgrind says I have 778 allocs, 354 frees on one run, but I thought I'd freed all my allocations!lzw.h: /** *  file: lzw.h * *  function declarations for lzw decompression algorithm. */#ifndef LZW_H#define LZW_H#include <stdio.h>#include <stdlib.h>typedef uint8_t byte;/** *  Frees all blocks between 'from' and 'to' (inclusive) in the dict. */void free_dict (byte* dict[], int from, int to);/** *  Enters the first 256 ASCII values into dict, and their respective sizes into sizes. */void load_dict_defaults (byte* dict[], int sizes[]);/** *  Reads 12 bits from source and returns them as an int. */int fread_twelve(FILE* source);/** *  LZW decompression of source to dest. *  Returns 0 if successful, a positive integer otherwise. */int decompress (FILE* source, FILE* dest);#endif // LZW_Hdecompress.c:/** *  file: decompress.c * *  implementation of LZW decompression algorithm using 12-bit fixed-width compression format. * *  usage: decompress source output */#include lzw.h#include <stdio.h>#include <stdlib.h>int main (int argc, char* argv[]){       // check for correct number of args    if (argc != 3)    {        printf(Usage: decompress source output\n);        return 1;    }    // open compressed file    FILE* source = fopen(argv[1], rb);    if (source == NULL)    {        printf(Error: cannot open %s\n, argv[1]);        return 1;    }    // open destination file    FILE* dest = fopen(argv[2], wb);    if (dest == NULL)    {        printf(Error: cannot open %s\n, argv[2]);        return 1;    }    // decompress    if (decompress(source, dest) == 1)    {        printf(Decompression failed\n);        fclose(source);        fclose(dest);        return 1;    }    else    {        fclose(source);        fclose(dest);        return 0;    }}lzw.c:/** *  file: lzw.c *   *   *  Implementation of LZW decompression algorithm using 12-bit fixed-width compression format. * */#include lzw.h#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdbool.h>byte* dictionary[4096];int dict_entry_sizes[4096];/** *  Enters the first 256 ASCII values into dict. */void load_dict_defaults (byte* dict[], int sizes[]){    byte* temp;    byte c = 0;    for (int i = 0; i < 256; i++)    {        temp = malloc(1);        temp[0] = c++;        dict[i] = temp;        dict_entry_sizes[i] = 1;    }}/** *  Frees all blocks between 'from' and 'to' (inclusive) in the dict. */void free_dict (byte* dict[], int from, int to){    for (int i = from; i <= to; i++)    {        free(dict[i]);    }}/** *  Gets 12 bits from source and returns them as an int. *  Returns 0 on an error or EOF. */int fread_twelve(FILE* source){    int value;    static bool left = true;    static byte prevBytes[2];    byte buf[3];    if (left)    {        if (fread(buf, 1, 2, source) != 2)         {            // reached eof: even number of entries            return 0;        }        if (fread(buf+2, 1, 1, source) != 1)        {            // reached eof: buf[0] and buf[1] are the padded last entry            value = (buf[0] << 8) | buf[1];        }        else        {            value = (buf[0] << 4) | (buf[1] >> 4);            left = false;            prevBytes[0] = buf[1];            prevBytes[1] = buf[2];        }    }    else    {        value = ((prevBytes[0] & 0x0F) << 8) | prevBytes[1];        left = true;    }    return value;}/** *  LZW decompression of source to dest. *  Returns 0 if successful, a positive integer otherwise. */int decompress (FILE* source, FILE* dest){    load_dict_defaults(dictionary, dict_entry_sizes);    int dictPos = 256;    int prevCode, currCode;    byte* prevString, *currString;    // read in the first character    currCode = fread_twelve(source);    size_t size = dict_entry_sizes[currCode];    currString = malloc(size + 1); //+1 for upcoming char    if (currString == NULL)    {        fprintf(stderr, decompress: error assigning currString.\n);        return 1;    }    memcpy(currString, dictionary[currCode], size);    fwrite(currString, size, 1, dest);    prevCode = currCode;    prevString = currString;    // read in the rest of the characters    size_t oldSize;    while ( (currCode = fread_twelve(source)) && currCode )    {               if (currCode > dictPos)        {            fprintf(stderr, decompress: currCode out of dictionary range.\n);            return 1;        }        oldSize = dict_entry_sizes[prevCode];        if (currCode < dictPos)     // code in dict        {            size = dict_entry_sizes[currCode];            currString = malloc(size + 1); // +1 for adding char on next loop            if (currString == NULL)            {                fprintf(stderr, decompress: error assigning currString.\n);                return 1;            }            memcpy(currString, dictionary[currCode], size);            fwrite(currString, size, 1, dest);            memcpy(prevString + oldSize, currString, 1);            dictionary[dictPos] = prevString;            dict_entry_sizes[dictPos++] = oldSize + 1;            // restart dict if full.             if (dictPos >= 4096)            {                free_dict(dictionary, 256, 4095);                dictPos = 256;            }        }        else    // code not in dict until this pass        {            memmove(prevString + oldSize, prevString, 1);   // memmove allows overlap            size = oldSize + 1;            fwrite(prevString, size, 1, dest);            dictionary[dictPos] = prevString;            dict_entry_sizes[dictPos++] = size;             currString = malloc(size + 1);  // +1 for adding char on next loop            if (currString == NULL)            {                fprintf(stderr, decompress: error assigning currString.\n);                return 1;            }            memcpy(currString, prevString, size);        }        prevCode = currCode;        prevString = currString;    }    free(prevString);           free_dict(dictionary, 0, dictPos-1);    return 0;}
LZW decompressor in C (II)
beginner;c;compression
A few recommendations you might find useful:Cleanup the global/static data:Right now your program make use of some global/static data. E.g.: dictionary, dict_entry_sizes, prevBytes and left (anything else that I've missed?). This poses a few limitations. For instance, your decompression routines cannot be run in parallel, since there is only one shared dictionary and friends, so running that code in different threads would result in data races. A good compression library should definitely be suitable for concurrent use!Another potential issue is that it would be much harder to expand your code to support multiple compressors that can be paused and resumed, again because they would all share the same common global variables.To improve this, you could make use of a context structure for your compressor. If you haven't experimented with structs in C yet, this should be a great opportunity. Move all the global and static variables into a structure declared it in your header file. Example:struct LzwCompression{    bool left;    byte prevBytes[2];    byte* dictionary[4096];    int dict_entry_sizes[4096];};Now each of the decompression functions must take a pointer to this context struture and source the variables from it, instead of reading the globals. Example with decompress():int decompress (LzwCompression* lzw, FILE* source, FILE* dest){    load_dict_defaults(lzw->dictionary, lzw->dict_entry_sizes);    lzw->left = true;    ...And the caller would then provide an instance of this structure to the decompression functions:LzwCompression lzw;decompress(&lzw, sourceFile, destFile);// You can have as many 'LzwCompressions' as you need, without the danger of they stepping on each other's toes.After this change is applied, be sure to properly initialize the the structure. Your code probably relies on the globals being zero initialized. The compiler does that for you for global variables, but a struct declared at function scope is not cleared by default. So you might memset() the LzwCompression instance either on main() or right at the top of decompress(). Also set the left field to true after memseting the structure, since that was the starting value of the global originally.Hide implementation details:Internal functions that are only relevant to the implementation in the .c file should not be exposed in your header file. Keep header files as lean and mean as you can to streamline compile times. I believe the only function the user of your decompression cares about is decompress(), so remove the prototypes of the other ones from the header file and place those functions at the top of your .c file, marking them as static. In this case the static keyword means that the function is local, so the linker doesn't export it. It is not quite the same meaning of a static variable, so you don't have to worry with shared/global data. // lzw.cstatic void load_dict_defaults (byte* dict[], int sizes[]){    ...}static void free_dict (byte* dict[], int from, int to){    ...}.. etc ...Odds and ends:I see that you already use the bool type, so extend that to the return type of functions that only have two possible outcomes, success (true) and failure (false). Returning integer error codes is error prone for the programmer since there isn't a single agreement or convention on which values should be returned. A boolean is much more obvious of its meaning.Go easy on the magic numbers. You have a lot of repeated literal constants out there that could be consolidated into a few named constants. #define and enum are the traditional C ways of doing that, but I'd instead recommend preferring static consts whenever possible to gain some type safety.You correctly use stderr in your decompression routines to print errors, but in main() all error reporting is done with printf, which outputs to stdout. Error output should always be sent to stderr, so that users can easily filter the program output. Replace those error prints with stderr. You might also consider adding the output of strerror() to the messages involving system errors (like fopen() and friends).Well, that's all the juice I have! Compression is not my field, so I don't know how you can improve the algorithm. Hopefully someone else will provide some comments on that. As for the memory leaks you mention, I haven't tested, sorry. If the output of Valgrind is not helping you much, you might try the Clang LeakSanitizer which is a very powerful tool with a super detailed error output that should make it very easy to find the source of the leaks.EDIT: In a second thought, I think I do know where your memory leaks are:void load_dict_defaults (byte* dict[], int sizes[]){    byte* temp;    byte c = 0;    for (int i = 0; i < 256; i++)    {        temp = malloc(1); // <-- is this ever freed somewhere?        temp[0] = c++;        dict[i] = temp;        dict_entry_sizes[i] = 1;    }}If I follow correctly the logic of decompress(), that 1 byte malloc you did in the above function gets overwritten without ever being freed. But actually, mallocing a single byte of memory is very inefficient, so you should try to refactor that to instead perform a single allocation of the sum of all bytes. I'll leave that as an exercise ;).