First off, your prof apparently wants you to become familiar with walking a pointer through a collection of both strings (the labels) and numbers (the floating-point values) using pointer arithmetic without using array indexing. A solid pointer familiarity assignment.
To handle the labels you can use a pointer to pointer to type char (a double pointer) as each pointer will point to an array of chars. You can declare and allocate pointers for labels as follows. (this assumes you have already read the rows
and cols
values from the input file)
char buf[MAXC] = "", /* temporary line buffer */
**labels = NULL, /* collection of labels */
**lp = NULL; /* pointers to walk labels */
...
/* allocate & validate cols char* pointers */
if (!(labels = calloc (rows, sizeof *labels))) {
fprintf (stderr, "error: virtual memory exhausted.\n");
return 1;
}
You can do the same thing for your pointer values, except you only need a pointer to type double as you will simply need to allocate for a collection of doubles.
double *mtrx = NULL, /* collection of numbers */
*p; /* pointers to walk numbers */
...
nptrs = rows * cols; /* set number of poiners required */
/* allocate & validate nptrs doubles */
if (!(mtrx = calloc (nptrs, sizeof *mtrx))) {
fprintf (stderr, "error: virtual memory exhausted.\n");
return 1;
}
The use of the pointers lp
and p
are crucial because you cannot increment either labels
or mtrx
(without saving the original address) because doing so will lose the pointer to the start of the memory allocated to each, immediately causing a memory leak (you have no way to free the block) and preventing you from ever being able to access the beginning again. Each time you need to walk over labels
or mtrx
just assign the start address to the pointer, e.g.
p = mtrx; /* set pointer p to mtrx */
lp = labels; /* set poiners lp to labels */
Now you are free to read and parse the lines in any manner you choose, but I would strongly recommend using line-oriented-input functions to read each line into a temporary line buffer, and then parse the values you need using sscanf
. This has many advantages to reading with fscanf
alone. After you read each line, you can parse/validate each value before allocating space for the strings and assigning the values.
(note: I cheat below with a single sscanf
call, where you should actually assign a char*
pointer to buf
, read the label
, then loop cols
number of times (perhaps using strtok/strtod
) checking each value and assigning to mtrx
, -- that is left to you)
/* read each remaining line, allocate/fill pointers */
while (ndx < rows && fgets (buf, MAXC, fp)) {
if (*buf == '\n') continue; /* skip empty lines */
char label[MAXC] = ""; /* temp storage for labels */
double val[cols]; /* temp storage for numbers */
if (sscanf (buf, "%s %lf %lf %lf %lf", /* parse line */
label, &val[0], &val[1], &val[2], &val[3]) ==
(int)(cols + 1)) {
*lp++ = strdup (label); /* alloc/copy label */
for (i = 0; i < cols; i++) /* alloc/copy numbers */
*p++ = val[i];
ndx++; /* increment index */
}
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
Then it is simply a matter of looping over the values again, using or outputting as needed, and then free
ing the memory you allocated. You could do that with something similar to:
p = mtrx; /* reset pointer p to mtrx */
lp = labels; /* reset poiners lp to labels */
for (i = 0; i < rows; i++) {
printf (" %-10s", *lp);
free (*lp++);
for (j = 0; j < cols; j++)
printf (" %7.2lf", *p++);
putchar ('\n');
}
free (mtrx); /* free pointers */
free (labels);
That's basically one of many approaches. Putting it all together, you could do:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { MAXC = 512 }; /* constants for max chars per-line */
int main (int argc, char **argv) {
char buf[MAXC] = "", /* temporary line buffer */
**labels = NULL, /* collection of labels */
**lp = NULL; /* pointers to walk labels */
double *mtrx = NULL, /* collection of numbers */
*p; /* pointers to walk numbers */
size_t i, j, ndx = 0, rows = 0, cols = 0, nptrs = 0;
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
return 1;
}
while (fgets (buf, MAXC, fp)) /* get rows, ignore blank lines */
if (sscanf (buf, "%zu", &rows) == 1)
break;
while (fgets (buf, MAXC, fp)) /* get cols, ignore blank lines */
if (sscanf (buf, "%zu", &cols) == 1)
break;
if (!rows || !cols) { /* validate rows & cols > 0 */
fprintf (stderr, "error: rows and cols values not found.\n");
return 1;
}
nptrs = rows * cols; /* set number of poiners required */
/* allocate & validate nptrs doubles */
if (!(mtrx = calloc (nptrs, sizeof *mtrx))) {
fprintf (stderr, "error: virtual memory exhausted.\n");
return 1;
}
/* allocate & validate rows char* pointers */
if (!(labels = calloc (rows, sizeof *labels))) {
fprintf (stderr, "error: virtual memory exhausted.\n");
return 1;
}
p = mtrx; /* set pointer p to mtrx */
lp = labels; /* set poiners lp to labels */
/* read each remaining line, allocate/fill pointers */
while (ndx < rows && fgets (buf, MAXC, fp)) {
if (*buf == '\n') continue; /* skip empty lines */
char label[MAXC] = ""; /* temp storage for labels */
double val[cols]; /* temp storage for numbers */
if (sscanf (buf, "%s %lf %lf %lf %lf", /* parse line */
label, &val[0], &val[1], &val[2], &val[3]) ==
(int)(cols + 1)) {
*lp++ = strdup (label); /* alloc/copy label */
for (i = 0; i < cols; i++) /* alloc/copy numbers */
*p++ = val[i];
ndx++; /* increment index */
}
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
p = mtrx; /* reset pointer p to mtrx */
lp = labels; /* reset poiners lp to labels */
for (i = 0; i < rows; i++) {
printf (" %-10s", *lp);
free (*lp++);
for (j = 0; j < cols; j++)
printf (" %7.2lf", *p++);
putchar ('\n');
}
free (mtrx); /* free pointers */
free (labels);
return 0;
}
Example Input File Used
$ cat dat/arrinpt.txt
3
4
abc123 8.55 5 0 10
cdef123 83.50 10.5 10 55
hig123 7.30 6 0 1.9
Example Use/Output
$ ./bin/arrayptrs <dat/arrinpt.txt
abc123 8.55 5.00 0.00 10.00
cdef123 83.50 10.50 10.00 55.00
hig123 7.30 6.00 0.00 1.90
Memory Use/Error Check
In any code your write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.
It is imperative that you use a memory error checking program to insure you haven't written beyond/outside your allocated block of memory, attempted to read or base a jump on an uninitialized value and finally to confirm that you have freed all the memory you have allocated. For Linux valgrind
is the normal choice.
$ valgrind ./bin/arrayptrs <dat/arrinpt.txt
==22210== Memcheck, a memory error detector
==22210== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==22210== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==22210== Command: ./bin/arrayptrs
==22210==
abc123 8.55 5.00 0.00 10.00
cdef123 83.50 10.50 10.00 55.00
hig123 7.30 6.00 0.00 1.90
==22210==
==22210== HEAP SUMMARY:
==22210== in use at exit: 0 bytes in 0 blocks
==22210== total heap usage: 5 allocs, 5 frees, 142 bytes allocated
==22210==
==22210== All heap blocks were freed -- no leaks are possible
==22210==
==22210== For counts of detected and suppressed errors, rerun with: -v
==22210== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)
Always confirm All heap blocks were freed -- no leaks are possible and equally important ERROR SUMMARY: 0 errors from 0 contexts. Note: some OS's do not provide adequate leak and error suppression files (the file that excludes system and OS memory from being reported as in use) which will cause valgrind
to report that some memory has not yet been freed (despite the fact you have done your job and freed all blocks you allocated and under your control).
Look things over and let me know if you have any questions.