4

Hello I attempting to write a C program that creates an 800x800 pixel PGM file and then populates the file with 100x100 pixel alternating black and white squares. It compiles fine, freezes when executing, and the resulting PGM file appears to only be a thin alternating black and white line.

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

int main (int argc, char* argv[])
{
    /*Declaring variables for rows, columns, boardsize, squaresize and pgmData 
    array*/
    int row, col,i,j;
    int iBoardSize = 800, iSquareSize = 100;
    int **iPgmData;

    /*Allocate memory for the data*/
    row = 800;
    col = 800;

    iPgmData = malloc(row * sizeof(int *));
    for (i = 0; i < row;i++)
        iPgmData[i] = malloc(col * sizeof(int));

    /*Assign data to the array the desired result is an 8x8 checkboard of 100x100
    pixel squares of alternating black and white.*/
    for (row = 0; row < iBoardSize; row++){
        for (col = 0; col < iBoardSize; col++){
            if ((row / iSquareSize + col / iSquareSize ) %2 == 0)
                iPgmData[row][col] = 0;
            else
                iPgmData[row][col] = 255;

        }
    }

    /* Open the PGM file */
    FILE* image = fopen(argv[1], "wb");
    if (image == NULL){
      fprintf(stderr, "Can't open output file %s!\n", argv[1]);
      exit(1);
    }

    /*Write the header*/
    fprintf(image, "P2\n%d %d\n255\n", iBoardSize, iBoardSize);

    for (row = 0;row <= iBoardSize; row++){
        for (col = 0;col <= iBoardSize; col++){
            if (col < iBoardSize)
                fprintf(image, "%d ", iPgmData[row][col]);
            else
                fprintf(image, "\n");
        }
    }

    /*Write pgmData*/
    fwrite(iPgmData, 1, iBoardSize*iBoardSize*sizeof(int *), image);

    /*Close the PGM*/
    fclose(image);

    /*Free allocated memory*/
    free(iPgmData);

    return 0;
}
  • It's great that there's still people out there learning to code graphics in C. Up you go! – Nicolas Miari Feb 03 '16 at 06:08
  • 1) when calling any of the memory allocation functions, always check (!=NULL) the returned value to assure the operation was successful. 2) for ease of understanding and readability by us humans, please follow the axiom: *only one statement per line and (at most) one variable declaration per statement.* 3) never access argv[] beyond argv[0] without first checking that `argc` indicates such a command line parameter exists. – user3629249 Feb 03 '16 at 16:23
  • when compiling, always enable all the warnings, then fix those warnings. (for `gcc`, at a minimum use: ``-Wall -Wextra -pedantic` (I also use: `-Wconversion -std=gnu99`) ) – user3629249 Feb 03 '16 at 16:25
  • the posted code contains several 'magic' numbers. 'magic' numbers make the code much more difficult to understand, debug, maintian. 'magic' numbers like: 100, 255, 800. Suggest: use `#define`s or an `enum` to give those 'magic' numbers meaningful names, then use those meaningful names throughout the code. – user3629249 Feb 03 '16 at 16:29
  • 1
    per this web page: `http://netpbm.sourceforge.net/doc/pgm.html` the magic numbers for .pgm (gray scale) images is `P5` not `P2` – user3629249 Feb 03 '16 at 16:54
  • regarding these two lines: `for (row = 0;row <= iBoardSize; row++) { for (col = 0;col <= iBoardSize; col++)`. in C, offsets into an array start with 0 and proceed to (number of elements in array -1). Suggest: `for (row = 0; row < iBoardSize; row++) { for (col = 0; col < iBoardSize; col++)` – user3629249 Feb 03 '16 at 16:57
  • per `http://netpbm.sourceforge.net/doc/pgm.html` the format of a grayscale image (.pgm) does not have a other characters in the image but the gray scale values, I.E. not '\n' at the end of each row. – user3629249 Feb 03 '16 at 17:00

2 Answers2

6

Problems I see:

Using out of bounds array index

for (row = 0;row <= iBoardSize; row++){
             ^^^^^^^^^^^^^^^^ Wrong. Should be row < iBoardSize
   for (col = 0;col <= iBoardSize; col++){
                ^^^^^^^^^^^^^^^^ Wrong. Should be col < iBoardSize
      if (col < iBoardSize)
         fprintf(image, "%d ", iPgmData[row][col]);
      else
         fprintf(image, "\n");
   }
}

That block can be simplified to:

for (row = 0;row < iBoardSize; row++){
   for (col = 0;col < iBoardSize; col++){
      fprintf(image, "%d ", iPgmData[row][col]);
   }
   fprintf(image, "\n");
}

Call to fwrite

fwrite(iPgmData, 1, iBoardSize*iBoardSize*sizeof(int *), image);

You can use fwrite only if you have contiguous data. In your case, you don't. It's not clear to me what you hoped to accomplish from that call. I removed the call and the output file is a valid PGM file.

Not freeing the allocated memory correctly

You have

free(iPgmData);

That only deallocates the memory allocated for the pointers. It does not deallocated the memory the pointers point to. You need to use:

for (i = 0; i < row;i++)
   free(iPgmData[i]);

free(iPgmData);
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • 1
    Correct, except the inner loop is obviously intended to be `<=` because of the branchy test for writing newline. This could have been achieved better by writing the newline _after_ the inner loop. – paddy Feb 03 '16 at 06:00
  • I think main problem in question is mentioned `fwrite` call. There should be either a separate call for every image row, or the image in memory should be stored in single-dimension array. – Ryzhehvost Feb 03 '16 at 09:22
  • Thanks for the help! Paddy is correct in that my intention was to use basically iBoardSize+1 to send a new line, I note that what you suggested also accomplishes this. Also regarding memory allocation and freeing, would using talloc work better here? I came across it while searching and it seems like: `#define talloc(type, num) (type *) malloc(sizeof(type)*(num))` would allow for later using: `iPgmData = talloc(int *, row); for (i = 0; i < row;i++) iPgmData[i] = talloc(int, col); free(iPgmData);` I might not understanding the formatting associated with talloc. – Blair McKinney Feb 03 '16 at 16:13
  • @BlairMcKinney, whether you use `malloc` or `talloc`, you'll still have to call `free` like I suggested. – R Sahu Feb 03 '16 at 18:56
2

the posted program is way over complex and contains several problems regarding indexing into arrays and how the data is being written to the output file.

The following code incorporates the comments, performs appropriate error checking and produces the desired image file.

#include <stdio.h>  // fopen(), fclose(), fprintf(), FILE
#include <stdlib.h>

#include <errno.h>  // errno
#include <string.h> // strerror()

#define MAX_ROWS    (800)
#define MAX_COLS    (800)
#define BOARD_SIZE  (800)
#define SQUARE_SIZE (100)

int main (int argc, char* argv[])
{
    int row; // image row index
    int col; // image column index


    if( 2 != argc )
    { // then required command line parameter missing
        fprintf( stderr, "USAGE: %s <PGM_filename>\n", argv[0]);
        exit( EXIT_FAILURE );
    }

    // implied else, correct number of command line parameters

    /* Open the PGM file */
    FILE* image = fopen(argv[1], "wb");
    if (!image)
    {
      fprintf(stderr, "Can't open output file %s for write, due to: %s\n", argv[1], strerror( errno ) );
      exit( EXIT_FAILURE);
    }

    /*Write the header*/
    fprintf(image, "P5\n%d %d\n255\n", BOARD_SIZE, BOARD_SIZE);

    /* write the gray scale image */
    for (row = 0;row < BOARD_SIZE; row++)
    {
        for (col = 0;col < BOARD_SIZE; col++)
        {

            if( (row/SQUARE_SIZE)%2 == 0 )
            {
                if( (col/SQUARE_SIZE)%2 == 0 )
                {
                    fprintf( image, "%c", 255 );
                }

                else
                {
                    fprintf( image, "%c", 0 );
                }
            }

            else
            {
                if( (col/SQUARE_SIZE)%2 == 0 )
                {
                    fprintf( image, "%c", 0 );
                }

                else
                {
                    fprintf( image, "%c", 255 );
                }
            }
        }
    }

    fclose(image);

    return 0;
} // end function: main
user3629249
  • 16,402
  • 1
  • 16
  • 17
  • Nice and straightforward, but why do you have a habit of adding parentheses around your `#define`d constants? It's not as if that would help with a malformed expression such as `x = MAX_ROWS123;` (just curious where you picked that up). – Jongware Feb 03 '16 at 18:30
  • 1
    @Jongware, The reason for adding parens around the body of a #define is because the `text replacement` operation, performed by the preprocessor step of the compiler does not understand all the ramifications of the context where that #define is invoked. Therefore, the resulting code can `run together` with the surrounding environment, resulting in something other than what the programmer intended. The preprocessor concatenation operator (##) will cause issues. Same for stringization (#). – user3629249 Feb 03 '16 at 19:14
  • @Jongware, the following code displays incorrect output due to the incorrect definition (no parens around each of the parameter usages) of the #define: `#define SQR(x) (x*x) int main() { int a, b=3; a = SQR(b+5); printf("%d\n",a); return 0; }` – user3629249 Feb 03 '16 at 19:16
  • Thanks! You indeed refer to 2 relatively new (from my point of view ) uses of macros, which I am not (yet) comfortable to use - and so it explains I've never encountered a problem with missen parens. I guess I'll have to keep an eye out for this when I start using such syntax. (Your 2nd example is one I am dearly familiar with - and it's something else than your constants.) – Jongware Feb 03 '16 at 19:17
  • @Jongware, you might want to look at: `http://stackoverflow.com/questions/7186504/c-macros-and-use-of-arguments-in-parentheses/7186517#7186517` for a more mundane example. – user3629249 Feb 03 '16 at 19:19
  • Awesome and thorough answer. One question: You referred to "magic numbers" in a previous comment, in your provided example would the 0 and 255 still qualify as magic numbers that should be given more meaningful names? The current version I have locally I created iBlack with a value of 0 and iWhite with a value 255 based on your earlier feedback. – Blair McKinney Feb 06 '16 at 04:25
  • usually numbers 0 and 1 are not considered 'magic' (hardcoded) numbers. Almost all other hardcoded numbers should be given meaningful names, Yes, I should have provided a #define meaningful name for the 255. My sloppy coding to have not done so. – user3629249 Feb 06 '16 at 06:00
  • Yes, you should keep the #defines for black and white – user3629249 Feb 06 '16 at 06:04