1

So I'm currently working on a project that uses data from a txt file. The user is prompted for the filename, and the first two lines of the txt file are integers that essentially contain the row and column values of the txt file. There are two things that are confusing me when writing this program the way my instructor is asking. For the criteria, she says:

  1. read in the data and place into an array of data and
  2. your code should access memory locations via pointers and pointer arithmetic, no []'s in the code you submit.

The left-most column is an identifier while the rest of the row should be considered as that rows data (floating point values). An example of what the file might contain is:

3
4
abc123  8.55    5   0   10
cdef123  83.50 10.5 10  55
hig123   7.30   6   0   1.9

My code:

//Creates array for 100 characters for filename
char fileName[100];

printf("Enter the file name to be read from: ");
scanf("%s", fileName);

FILE *myFile;
myFile = fopen(fileName, "r");

//Checks if file opened correctly
if (myFile == NULL)
{
    printf("Error opening file\n"); //full file name must be entered
}
else {
    printf("File opened successfully\n");
}

//gets value of records and value per records from file
//This will be the first 2 lines from line
fscanf(myFile, "%d %d", &records, &valuesPerRecords);

//printf("%d %d\n", records, valuesPerRecords); //Check int values from file
int counter = 0;
char *ptr_data;

ptr_data = (char*)malloc(records*(valuesPerRecords));

int totalElements = records*(valuesPerRecords);

/*If malloc can't allocate enough space, print error*/
if (ptr_data == NULL) {
    printf("Error\n");
    exit(-1);
}

int counter;

for (counter = 0; counter < totalElements; counter++){
    fscanf(myFile, "%s", &ptr_data);
}

so I'm wondering if so far, I'm on the right track. I can't seem to think of a way to have the first column read in as a string, while the rest is read in as integers. I'll also have to use the stored values later and sort them but that's a different problem for a later date.

Woff
  • 15
  • 5
  • Assuming you have to use `fscanf()` to read the file (there are alternatives) look up the `%s` format specifier. – Peter Sep 11 '16 at 00:53
  • Not a complete answer, because this is homework, but: you might want to ask about those “integers,” since they look like floating-point numbers to me. Anyway, some hints. Check out `fscanf()`, `sscanf()` and `calloc()`, find out the format of each line first, create your array, read in the lines using a loop, and read in the contents of each line using a nested loop. – Davislor Sep 11 '16 at 00:58
  • I'm seeing `5-values` per-row. Are you sure your input file is correct? (It can be, you will just have whitespace in the final value in each row) – David C. Rankin Sep 11 '16 at 01:51
  • When you say "the rest of the row should be considered as integers", what does that mean when the value is '8.55'? Should that be 8 or 9 (or something else) and why? (If it isn't already obvious, what about 83.50, 7.30, 1.9?) – Jonathan Leffler Sep 11 '16 at 02:17
  • @DavidC.Rankin: I think the data lines consist of one blank-free label plus (in this sample) 4 numbers that have to be treated as integers somehow despite have decimal points and non-zero fractional parts. – Jonathan Leffler Sep 11 '16 at 02:19
  • @JonathanLeffler sorry, I've updated that. The rows can and should contain floating point values. – Woff Sep 11 '16 at 02:35
  • @JonathanLeffler yes that is correct, except I made a mistake originally. The rows can contain floating point values. The left-most column is an identifier while the integers ( 3, 4) are meant to describe the size of the floating point data – Woff Sep 11 '16 at 02:42
  • Oh, that makes sense now. The floating-point issue wasn't immediately apparent as it didn't seem germane to the *advance a pointer* issue. – David C. Rankin Sep 11 '16 at 02:53
  • @Woff: Thanks for clarifying the `double` vs `int` issue — that it should be `double` makes sense. One problem is `ptr_data = (char*)malloc(records*(valuesPerRecords));` which doesn't allocate enough space. It allocates, with your sample data, 12 bytes. That isn't enough. Have you learned about structures? What about flexible array members (FAM)? Is there an upper bound on the length of a label? Given that you're told how many records there will be, and how long the array of values for each record is, it is certainly possible to solve the problems; the difficulty is knowing what you plan. – Jonathan Leffler Sep 11 '16 at 03:53
  • You may need to read up on [How to use `sscanf()` in loops?](http://stackoverflow.com/questions/3975236/how-to-use-sscanf-in-loops/) – Jonathan Leffler Sep 11 '16 at 04:00

1 Answers1

3

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 freeing 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.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • after using printf %p. I figured out the last `p = mtrx;` the last one is not that intuitive for me. I first feel like you assigned null to p. but it's pointer address reassign to beginning after loop. – jian Aug 05 '22 at 11:28
  • @jian I use `p` to iterate over each pointer in `mtrx` filling `mtrx` with `*p++ = val[i];`. After the loop `p` is one-past the end of `mtrx`, so before the print loop where we use `p` again, we reset it `p = mtrx;` so it points to the beginning of `mtrx` again. Great job in working through and understanding the examples. Once you work with pointers enough, bingo - the light-bulb comes on and you get it. Whether it's a pointer with one-level of redirection, e.g. `char *p;` or two-levels of redirection, `char **p`, pointers work the same. The final dereference gives access to the value. – David C. Rankin Aug 06 '22 at 03:36