1

My goal is to pass a pointer to double to a function, dynamically allocate memory inside of the function, fill resulted array with double values and return filled array. After lurking attentively everywhere in StackOverflow, I have found two related topics, namely Initializing a pointer in a separate function in C and C dynamically growing array. Accordingly, I have tried to write my own code. However, the result was not the same as it was described in aforementioned topics. This program was run using both gcc and Visual Studio.

First trial.

int main()
{
    double *p;
    int count = getArray(&p);
    <...print content of p...>
    return 0;
}

int getArray(double *p)
{
    int count = 1;
    while(1)
    {
        if(count == 1)
            p = (double*)malloc(sizeof(double));
        else 
            p = (double*)realloc(p, count*sizeof(double));
        scanf("%lf", &p[count-1]);
        <...some condition to break...>
        count++;
    {
    <... print the content of p ...>
    return count;
}

(Here comes the warning from compiler about incompatible argument type. Ignore it).

Input:

1.11
2.22
3.33

Output:

1.11
2.22
3.33

0.00
0.00
0.00

Second trial.

int main()
{
    double *p;
    int count = getArray(&p);
    <...print content of p...>
    return 0;
}

int getArray(double **p)
{
    int count = 1;
    while(1)
    {
        if(count == 1)
            *p = (double*)malloc(sizeof(double));
        else 
        {
            double ** temp = (double*)realloc(*p, count*sizeof(double));
            p = temp;
        }
        scanf("%lf", &(*p)[count-1]);
        <...some condition to break...>
        count++;
    {
    <... print the content of p ...>
    return count;
}

Input:

1.11
2.22
Segmentation error.

I tried this method on several different *nix machines, it fails when the loop uses realloc. SURPRISINGLY, this code works perfect using Visual Studio.

My questions are: first code allows to allocate and reallocate the memory and even passes all this allocated memory to main(), however, all the values are zeroed. What is the problem? As for the second program, what is the reason of the segmentation error?

tenghiz
  • 239
  • 1
  • 10
  • `if(count = 0)` you mean `if(count == 0)`? Does your compiler emit any warnings? It might give you a warning for this one. – Blaze Jun 06 '19 at 07:26
  • Why was the "trick" useless? Unless there is something you have not mentioned, it is exactly the correct solution to your problem. – rtoijala Jun 06 '19 at 07:26
  • @Blaze, sorry for "==" operator. As for other parts, no warnings from compiler. – tenghiz Jun 06 '19 at 07:28
  • @ rtoijala, this is all I have in my code. No other functions or actions. – tenghiz Jun 06 '19 at 07:29
  • @tenghiz Using a double pointer IS the right way to go. – klutt Jun 06 '19 at 07:32
  • If you pass a pointer to `double` to `getArray()`, the function will get a copy of the pointer value. When you `malloc` or `realloc` in the function you will modify the local copy only. This does not affect the pointer value in `main()`. That's why you get a segmentation fault in `main()`. I don't see a problem with the `scanf` input, even as you allocated the wrong size. If you get `0.00` output there may be a problem in the real code for `<...print content of p...>`. Using a `double **p` argument is a valid solution for an output parameter, but you have to change a few more things. – Bodo Jun 06 '19 at 07:47
  • The size for `realloc` is wrong. You have to use `(size + 1) * sizeof(double)`. If `count==0` you allocate space for 1 element, sou you have to allocate space for 2 elements if `count==1` etc. – Bodo Jun 06 '19 at 07:50
  • @ melpomene, I agree, this question can be seen as a duplicate. However, @Broman provided very useful information about casting inside of the function. Previously answered question lacks these things. – tenghiz Jun 07 '19 at 02:53
  • @tenghiz One way of solving such issues is to instead make that question a duplicate of this one. It outside my powers to fix it, and even if it was not, it would look bad if I fixed that since I'm the answerer. – klutt Jun 07 '19 at 06:01
  • "*Here comes the warning from compiler about incompatible argument type. Ignore it*" - No. Do not ignore type errors. – melpomene Jun 10 '19 at 11:29
  • If you want help with your crash, provide a [mcve]. – melpomene Jun 10 '19 at 11:30

1 Answers1

3

The right way of doing it is like this:

int getArray(double **p)
{
    int count = 0;
    while(1)
    {
        if(count == 0)
            *p = malloc(sizeof(**p));
        else 
            *p = realloc(*p, (count+1)*sizeof(**p));
        scanf("%lf", &((*p)[count]));
        <...some condition to break...>
        count++;
    {
    <...print content of p...>
    return count;
}

If you pass a pointer to a function and you want to change not only the value it is pointing at, but change the address it is pointing to you HAVE to use a double pointer. It is simply not possible otherwise.

And save yourself some trouble by using sizeof(var) instead of sizeof(type). If you write int *p; p = malloc(sizeof(int));, then you are writing the same thing (int) twice, which means that you can mess things up if they don't match, which is exactly what happened to you. This also makes it harder to change the code afterwards, because you need to change at multiple places. If you instead write int *p; p = malloc(sizeof(*p)); that risk is gone.

Plus, don't cast malloc. It's completely unnecessary.

One more thing you always should do when allocating (and reallocating) is to check if the allocation was successful. Like this:

if(count == 0) 
    *p = malloc(sizeof(**p));
else 
    *p = realloc(*p, (count+1)*sizeof(**p));

if(!p) { /* Handle error */ }

Also note that it is possible to reallocate a NULL pointer, so in this case the malloc is not necessary. Just use the realloc call only without the if statement. One thing worth mentioning is that if you want to be able to continue execution if the realloc fails, you should NOT assign p to the return value. If realloc fails, you will lose whatever you had before. Do like this instead:

int getArray(double **p)
{
    int count = 0;

    // If *p is not pointing to allocated memory or NULL, the behavior
    // of realloc will be undefined.
    *p = NULL;

    while(1)
    {
        void *tmp = realloc(*p, (count+1)*sizeof(**p));
        if(!tmp) {
            fprintf(stderr, "Fail allocating");
            exit(EXIT_FAILURE);
        }

        *p = tmp;

        // I prefer fgets and sscanf. Makes it easier to avoid problems
        // with remaining characters in stdin and it makes debugging easier

        const size_t str_size = 100;
        char str[str_size];
        if(! fgets(str, str_size, stdin)) {
            fprintf(stderr, "Fail reading");
            exit(EXIT_FAILURE);
        }

        if(sscanf(str, "%lf", &((*p)[count])) != 1) {
            fprintf(stderr, "Fail converting");
            exit(EXIT_FAILURE);
        }

        count++;

        // Just an arbitrary exit condition
        if ((*p)[count-1] < 1) {
            printf("%lf\n", (*p)[count]);
            break;
        }
    }
    return count;
}

You mentioned in comments below that you're having troubles with pointers in general. That's not unusual. It can be a bit tricky, and it takes some practice to get used to it. My best advice is to learn what * and & really means and really think things through. * is the dereference operator, so *p is the value that exists at address p. **p is the value that exists at address *p. The address operator & is kind of an inverse to *, so *&x is the same as x. Also remember that the [] operator used for indexing is just syntactic sugar. It works like this: p[5] translates to *(p+5), which has the funny effect that p[5] is the same as 5[p].

In my first version of above code, I used p = tmp instead of *p = tmp and when I constructed a complete example to find that bug, I also used *p[count] instead of (*p)[count]. Sorry about that, but it does emphasize my point. When dealing with pointers, and especially pointers to pointers, REALLY think about what you're writing. *p[count] is equivalent to *(*(p+count)) while (*p)[count] is equivalent to *((*p) + count) which is something completely different, and unfortunately, none of these mistakes was caught even though I compiled with -Wall -Wextra -std=c18 -pedantic-errors.

You mentioned in comments below that you need to cast the result of realloc. That probably means that you're using a C++ compiler, and in that case you need to cast, and it should be (double *). In that case, change to this:

double *tmp = (double*)realloc(*p, (count+1)*sizeof(**p));
if(!tmp) {
    fprintf(stderr, "Fail allocating");
    exit(EXIT_FAILURE);
}

*p = tmp;

Note that I also changed the type of the pointer. In C, it does not matter what type of pointer tmp is, but in C++ it either has to be a double* or you would need to do another cast: *p = (double*)tmp

klutt
  • 30,332
  • 17
  • 55
  • 95
  • In `realloc` use `(count+1)*sizeof(**p)`, because `count` is the number of elements already used. I think it must be `scanf("%lf", &(*p)[count]);` – Bodo Jun 06 '19 at 07:53
  • Should I use *tmp or **tmp? – tenghiz Jun 06 '19 at 08:31
  • And what about cast to malloc and realloc? (double*) or (double**)? – tenghiz Jun 06 '19 at 08:34
  • @tenghiz Do you mean in the declaration of `tmp`? It actually does not matter. You can safely assign pointers to each other as long as you don't dereference them. – klutt Jun 06 '19 at 08:34
  • I wrote in plain text that you should not cast malloc. – klutt Jun 06 '19 at 08:35
  • @Broman, thank you very much. All these pointer casts make me crazy. Especially scanf("%lf", &(*p)[count]). Honestly, I was trying to use scanf("%lf", **p[count]). It would be nice if you can give some explanation about sizeof(var). – tenghiz Jun 06 '19 at 09:07
  • Ok, guys, here is my last word. @Broman's solution doesn't work on *nix. I tried both Debian and Ubuntu machines, both say there is a realloc failure (second loop fails). But this solution works perfect on Visual Studio. HOWEVER, Visual Studio's compiler requires malloc and realloc both must be casted. Thank you all for your participation. – tenghiz Jun 06 '19 at 16:31
  • @tenghiz Then it sounds like you're compiling it with a C++ compiler. I don't know so much about Visual Studio, but in C you don't need to cast pointers, but you have to in C++. I'll look into the realloc issue in the weekend. – klutt Jun 06 '19 at 20:41
  • @tenghiz Answer updated – klutt Jun 06 '19 at 22:39