0

I am writing a C program in which I have to carry out a search with an unknown number of results, place the results in some kind of structure, and then access that structure from various places in the program.

This looks to me like a place where the C handle object (a pointer to a pointer) is going to work nicely. I give the structure I want the program to access a handle, a pointer which does not change, so that any part of the program can get it. I have that stable handle point at some kind of array, which will be made during the program, and whose address will change as the data it holds grows or shrinks.

C has all you need to do this: pointers which can point at other pointers; dynamic allocation of memory for expanding objects. But: to do this in C you have to know how to use * ** & in combination with brackets ()[] and -> and period (.). You have to know, for example, that *(place+j) is the same as place[j], as is (*place)[j] and *(*place+j), and exactly how place->thing, place.thing, and (*place)->thing differ. And so on. So although I have been writing C programs for thirty years, it still took me hours to get this right.

Here is the code for a working solution. I have a structure process_book_inf containing two handles, pointing at the data I want to access. I want one of those handles to access an array of integers search_els_found, which might be any length. I want the other to access an array of pointers to bounds_obj structures, where again there might be any number of such structures. Two functions testInt and testPtr create two such arrays, creating new pointers to the arrays as they grow, and then putting the addresses of the new arrays into the master process_book structure.

Here is my question: my code works, but I'm not sure it is as elegant, simple, and transparent as it might be. There are many people more skilled in C than I am. Anyone have any ideas? (As a side note: this example demonstrates how powerful C can be).

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

struct process_book_inf {
    void *search_els_found;
    void **search_texts_found;
};
typedef struct process_book_inf *process_book_ptr;

struct bounds_obj {
    int start_el;
    int start_off;
};
typedef struct bounds_obj *bounds_ptr;

void testInt(void **resultArray);
void testPtr(void **resultArray);

int main(int argc, const char * argv[]) {
    process_book_ptr testMemory=malloc(sizeof(struct process_book_inf));
    testInt((void **) &testMemory->search_els_found);
    for (int c=0; c<5; c++) {
        printf("retrieved %d\r", *((int *) (testMemory->search_els_found)+c));
    }
    testPtr((void **) &testMemory->search_texts_found);
    for (int c=0; c<5; c++) {
        bounds_ptr *dest= (bounds_ptr *) (testMemory->search_texts_found)+c;
        printf("retrieved structure values %d %d\r", (*dest)->start_el, (*dest)->start_off);
    }
    return 0;
}

void testInt(void **testarray) {
    int *place=(int *) malloc(sizeof(int));
    int **already=(int **) testarray;
    for (int c=0; c<5; c++) {
        if (c>0) {
            free(place);
            place=(int *) malloc((c+1) *sizeof(int));
            for (int j=0; j<c; j++){
                place[j]=(*already)[j];
 //             *(place+j)=*(*already+j);
            }
        }
         *(place+c)=c*100+c;
         printf("set %d\r", *(place+c));
        *already=place;
    }
}

//in this version, simplify as far as I can
//replace *(place+j) by place[j]; remove all redundant casts
void testPtr(void **textarray) {
    bounds_ptr *place= malloc(sizeof(bounds_ptr));
    bounds_ptr **already=(bounds_ptr **) textarray;
    for (int c=0; c<5; c++) {
        if (c>0) {
            free(place);
            place= malloc(sizeof(bounds_ptr)*(c+1));
            for (int j=0; j<c; j++){
                place[j]= (*already)[j];
             }
        }
        struct bounds_obj *dest= malloc(sizeof(struct bounds_obj));
        dest->start_el=(c*100)+1;
        dest->start_off=(c*100)+2;
        place[c]=dest;
        printf("set structure values %d %d\r", place[c]->start_el, place[c]->start_off);
        *already= place;
    }
}

The result of this is:

set 0
set 101
set 202
set 303
set 404
retrieved 0
retrieved 101
retrieved 202
retrieved 303
retrieved 404
set structure values 1 2
set structure values 101 102
set structure values 201 202
set structure values 301 302
set structure values 401 402
retrieved structure values 1 2
retrieved structure values 101 102
retrieved structure values 201 202
retrieved structure values 301 302
retrieved structure values 401 402
Program ended with exit code: 0
Peter Robinson
  • 363
  • 4
  • 17
  • Using `void*` or `void**` when you could just use the type is weird and not clean. Also casting return value of `malloc` is very much frowned upon. Using `*(something+other)` also is not recommended since it is confusing. Just use `something[other]`. And it seems you’ve become a three star programmer and trying to hide one with typedef. I’m not sure if this is really needed. Other than that the code looks confusing so not going to try to understand what it actually does. Allocations look wild. – Sami Kuhmonen Nov 30 '19 at 14:52
  • Yes, I have thought all these things too. What I would like to see is not just comments with kind-of half-suggestions, half-criticisms, but actual working suggestions. I can tell you that I am very aware of these points, and I have tried to simplify the program in exactly the ways you suggest. Yes, something like *((int *) (testMemory->search_els_found)+c) looks bizarre. But it works. And everything else I tried to simplify this, and much else, failed (in extremis, leading to bad memory accesses etc etc). The reason I posted this is that I am sure this can be done more elegantly. Help... – Peter Robinson Nov 30 '19 at 15:48
  • A small point: I note that indeed place[j] is the same as *(place+j) and I could have replaced those constructs by the simpler something[other]. I left the *(something+other) there as prompts. In the same way as we can simplify *(place+j) to place[j], what else can be simplified? – Peter Robinson Nov 30 '19 at 15:56
  • OK. Interesting. I read https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc, and matters are not as straight-forward as Sami asserts. Some argue for casting, some against. One writer in that discussion warns that in instances like this, where I am using pointer arithmetic to access array elements, casting might be needed. So I recompiled the program to check this, without the casts, and all still worked fine. To show this, I have edited part of the code above so that one of the examples uses casts, one does not. All is good either way. – Peter Robinson Dec 01 '19 at 13:12

0 Answers0