0jnats3
0jnats3

Reputation: 163

How to send 2d-array as argument to function that expects 1d-array in C?

This is an exercise from the book C Programming: A modern approach by K.N King

"Assume that the following array contains a week's worth of hourly temperature readings, with each row containing the readings for one day: int temperatures[7][24]; Write a statement that uses the search function to search the entire temperatures array for the value 32."

My code below includes my search function from a previous exercise, and main() where I call the function. Search simply processes an array and checks if any element is equal to a supplied argument "key". The solution appears to compile and execute the expected output, it prints "32 was found", or prints nothing if I comment out the assignment temperatures[5][22] = 32;.

Notice how the function takes a 1d array but the exercise is asking to process a 2d array.

I initially tried this solution without the explicit type cast (int*) in the function call, and got this when compiling (I reformatted it a bit):

1 compiler warning: "passing argument 1 of search from incompatible pointer type"

1 note: "expected 'const int *' but argument is of type 'int * [24]'"

Why do the warnings/note appear? If there is an incompatibility, why are they not errors? The type cast to int* removes any compiling issues, but is the solution actually correct/safe or bad C understanding/practice on my part? I understand that the function expects int* but that the 2d array decays to a pointer of type int * [24] without the type cast. Yet the code works in either case, albeit I just had one test case. How could I modify my solution logic to avoid this issue at all? The search function should stay the same though.

#include <stdbool.h>
#include <stdio.h>

bool search(const int a[], int n, int key);

int main(void){

    int temperatures[7][24] = {0};
    temperatures[5][22] = 32;
    bool has32 = search((int*)temperatures, 7 * 24, 32);

    if(has32)
        printf("32 was found");
    return 0;
}

bool search(const int a[], int n, int key){

    const int *p;

    for(p = a; p < a + n; p++)
        if(*p == key)
            return true;
    return false;
}

Upvotes: 0

Views: 229

Answers (4)

tstanisl
tstanisl

Reputation: 14167

This is alternative answer to a better solution.

Use a union of 2D and 1D array. The C standard allows inspecting one member via the other member:

union {
  int temperatures[7][24];
  int as_1d[7 * 24];
} u = { 0 };
u.temperatures[5][22] = 32;
bool has32 = search(u.as_1d, 7 * 24, 32);

Upvotes: 1

user9706
user9706

Reputation:

Pass the address of the first element but it's technically undefined behavior to index the array past column index 23:

bool has32 = search(&temperatures[0][0], 7 * 24, 32);

The better option is to change the prototype of search() to use a VLA:

bool search(size_t rows, size_t cols, const int a[rows][cols], int key) {
    for(size_t r = 0; r < rows; r++)
        for(size_t c = 0; c < cols; c++)
            if(a[r][c] == key)
               return true;
    return false;
}

and call it like this:

    bool has32 = search(
        sizeof(temperatures) / sizeof(*temperatures),
        sizeof(*temperatures) / sizeof(**temperatures),
        temperatures,
        32
    );

Upvotes: 5

H.S.
H.S.

Reputation: 12679

If you can change the search() function parameter type, change it to receive 2D array.
If you cannot then call the search() in a loop and in each iteration pass the element of 2D array temperatures to search(). The elements of 2D array temperatures are 1D array of type int [24] which can be passed to search().

In main() you should do:

    bool has32 = 0;
    for (int i = 0; i < 7; ++i) {
        has32 = search(temperatures[i], 24, 32);
        if (has32) break;
    }

This is perfectly compatible and will not invoke any kind of UB.

Also, instead of using numbers (like 7 and 24), may you give some name to them for better readability, like this

#define ROWS    7
#define COLUMNS 24

Use them in you program when defining array temperatures:

    int temperatures[ROWS][COLUMNS] = {0};
    int key = 32;

    temperatures[5][22] = key;

    bool has32 = 0;
    for (int i = 0; i < ROWS; ++i) {
        has32 = search(temperatures[i], COLUMNS, key);
        if (has32) break;
    }

Upvotes: 1

Lundin
Lundin

Reputation: 214890

First of all please check out this good, recent question: Is a two-dimensional array implemented as a continuous one-dimensional array? The answer by @dbush shows what the C language allows and doesn't allow in terms of mixing 1D arrays and 2D arrays. The answer by yours sincerely shows some possible work-arounds. You have to use one such work-around or you will be invoking undefined behavior (and the book you are reading ought to tell you as much unless it's crap).


I initially tried this solution without the explicit type cast (int*) in the function call, and got this when compiling (I reformatted it a bit):

1 compiler warning: "passing argument 1 of search from incompatible pointer type"

Because you can't wildly cast to int. A int* is not compatible with an int(*)[n], the code is a constraint violation, meaning it is not valid C. And you can't use an int* to iterate through a 2D array anyway.


Why do the warnings/note appear? If there is an incompatibility, why are they not errors?

Because the C language defines no such thing as "warnings/errors", it's a de facto standard invented by compilers. The C language only requires that some manner of diagnostic message is given when you feed the compiler invalid C. See What must a C compiler do when it finds an error?


but is the solution actually correct/safe or bad C understanding/practice on my part?

It is bad C as explained by the first link given in this answer.


Yet the code works in either case

Compilers tend to implement this manner of type punning in well-defined ways internally, but formally you are invoking undefined behavior. "It seems to work just fine" is one form of undefined behavior.


How could I modify my solution logic to avoid this issue at all? The search function should stay the same though.

As others have mentioned, the best way is to rewrite the function to use a 2D array instead. If that's not an option, you can use the union type punning example from my answer in the link at the top, then pass the union "arr_1d" member to the function.

Upvotes: 1

Related Questions