C - If value in array

I am new to C and am currently studying arrays. I would like to check if a certain value is in an array but I am running in some issues (not only syntax-wise but also from understanding-wise).

My goal is to have a function which I can call, give it two arguments - the value to search for and the array to search in - and get a 0 or 1 based on whether it was found or not back.

My approach is the following:

#include <stdio.h> #include <stdlib.h> int valueinarray(float val, float *arr[]); int main() { float arr[] = {5, 4.5, 4, 3.5, 3, 2.5,}; int test = valueinarray(4.5, *arr[]); printf("%d", test); return 0; } int valueinarray(float val, float *arr[]){ int i; for(i = 0; i < sizeof(*arr[]); i++){ if(*arr[i] == val) return 1; } return 0; } 

I have two questions now especially regarding the syntax:

  1. If I create a function with a pointer as one of its' parameters, do I have to refer to it using "*arr[]" inside the function the whole time? Or is "arr[]" or even "arr" enough?

  2. Do I understand it correctly that I am unable to pass a whole array to a function so I use a pointer instead?

Moreover, my approach is wrong and I do not see why. Iterating over the array seems to work just fine and even checking if a certain value is in it works as well, the issue seems to be in the way I call the function. I read about double pointers, is this a scenario where they're needed? If not, what are they needed for?

Thanks a lot.

9

2 Answers

Generic 1 Macro/Function for any value type

While there is an accepted answer, here is my generic implementation of the "problem." It allows you to use a single macro/function, for virtually any value type.

If you do not want to follow along, the source files can be downloaded from this repository. Just note that there are minor differences, this post is already very long as it is, and I did not want to make it any longer by including the other macros in the repo.

Repo Example

If you use the code from this post instead of the repo, you must also pass the size of the array. Example at the bottom

#include "generic_array.h" //Initialize Arrays uint16_t shortarray [10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; float floatarray[3] = {1.0f, 2.5f, -5.25f}; void main() { //Length not passed, full array checked bool containsShort = array_contains(5, shortarray); or bool containsFloat = array_contains(2.5f, floatarray); or //Length passed, half of array checked bool containsShort = array_contains(4, shortarray, (5 * sizeof(uint16_t)); } 

Generic Union

To start off we need to declare a union, which will allow us to pass any value type to our macro. The main reason we need this is so we can pass floating point numbers without them being cast to an integer, losing their true bits.

This union will allow us to use all (almost) value types up to 64 bits. So unfortunately "long double" will not be able to be used.

/* * Union which contains all value types. * Useful for converting from int types to and from floating types, without casting the bits. * Also makes _Generic macros which call functions to all value types possible. * * *Not all values are included. They need to be added. */ typedef union generic_union { const char* ccp; char*cp; char c; signed char sc; unsigned char usc; int i; unsigned int ui; short s; unsigned short us; long l; unsigned long ul; long long ll; unsigned long long ull; float f; double d; bool b; enum e; uint8_t u8; int8_t s8; uint16_t u16; int16_t s16; uint32_t u32; int32_t s32; uint64_t u64; int64_t s64; }generic_union_t; 

Generic Function

Then we need the function which can take any value(Except for structs).

/* * Searches an array for a value, and confirms its existence. * * /param value The value to find in the array. Can be any basic value type. * /param array Pointer to the start of the array. * /param arr_size The size of the array (in bytes). * /param elem_size The size of the value's type (in bytes). * * /returns If the array contains the value. */ bool array_contains_generic(const generic_union_t value, const char* array, size_t arr_size, uint8_t elem_size) { char* vp = &value; //Pointer to the value for(int i = 0; i < arr_size; i += elem_size) { uint8_t b = 0; for(b; b < elem_size; b++) { if(array[i + b] != vp [b]) { //Byte not matching, move to next element. break; } } if(b == elem_size) { return true; } } return false; } 

You could just use that function on its own, but we aren't able to use structs. Another problem is we will have to perform a (generic_union_t) cast each time, like the example below.

int array = {1, 2, 3, 4}; bool contains = array_contains_generic((generic_union_t)3, array, sizeof(array), sizeof(int)) 

So instead you can create a function that takes a pointer, but then you have to declare the value first, which is a little annoying.

int value = 3; bool contains = array_contains_memory(&value, array, sizeof(array), sizeof(int)) 

Ability to use structs

So instead of having 2 functions that mostly do the same thing, we will change the first function(array_contains_generic). It will take our value from the stack, place it in a variable, pass it as a pointer to our second function(array_contains_memory).

/* * Searches an array for a matching segment of memory. * * /param value Any value or struct that is in memory. * /param array Pointer to the start of the array. * /param arr_size The size of the array (in bytes). * /param elem_size The size of the value's type (in bytes). * * /returns If the array contains the value. */ bool array_contains_memory(const uint8_t* value, const uint8_t* array, size_t arr_size, uint8_t elem_size ) { for(int i = 0; i < arr_size; i += elem_size) { uint8_t b = 0; for(b; b < elem_size; b++) { if(array[i + b] != value[b]) { //Byte not matching, move to next element. break; } } if(b == elem_size) { return true; } } return false; } /* * Searches an array for a value, and confirms its existence. * * /param value The value to find in the array. Can be any basic value type. * /param array Pointer to the start of the array. * /param arr_size The size of the array (in bytes). * /param elem_size The size of the value's type (in bytes). * * /returns If the array contains the value. */ bool array_contains_generic(const generic_union_t value, const char* array, size_t arr_size, uint8_t elem_size) { uint64_t v = value.u64; //Store value so we can get pointer return array_contains_memory(&v, array, arr_size, elem_size); } 

But this still hasn't fixed our casting problem, and it would also be nice to just be able to call "array_contains" instead of "array_contains_..."

Macro (Adds the ability to use one "function prototype" for all use cases)

So here is when the macro comes into play, This allows us to call "array_contains" with literally any value type, including custom structs of any size. Another benefit is we don't have to pass "elem_size" anymore, as the macro extracts the size by using sizeof(array[0]).

/* * Macro used to "overload" array_indexOf functions. * Can be used with arrays or pointers. * * /param value The value to search for. * /param array Pointer to the start of the array. * /param length The size of the array (in bytes). * * /returns If the array contains the value. */ #define array_contains(value, array, length) _Generic((value), \ const char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length, sizeof(array[0])), \ char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length, sizeof(array[0])), \ const unsigned char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length,sizeof(array[0])), \ unsigned char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length, sizeof(array[0])), \ const signed char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length, sizeof(array[0])), \ signed char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length, sizeof(array[0])), \ float : internal_call_array_contains_special((generic_union_t)value, array, length, sizeof(float)), \ double : internal_call_array_contains_special((generic_union_t)value, array, length, sizeof(double)), \ default: array_contains_generic((generic_union_t)value, array, length, sizeof(array[0]))) 

This macro uses _Generic, which will call different functions depending on the type of "value." As you can see, there is still casting involved, but it is all done behind the scenes.

2 Parameter Function/Macro

And then finally, the OP wanted a function with only 2 parameters. That is achieved with another macro. Just note that it will not work with pointer arrays, they must be declared as an actual array.

/* * "array_contains" without the length parameter. */ #define array_contains_2(value, array) array_contains(value, array, sizeof(array)) 

The repo I posted at the top handles things a bit differently. Instead of having 2 macros for each set of parameters, you are able to call a single macro, like the example under the repo link.

Examples

Here are a few examples of how to use the macro with different data types.

//The struct def for the example typedef struct example_struct{ int a; float b; }example_struct_t; //The arrays to search example_struct_t ts_arr[4]; uint16_t shortarray [4] = {1, 2, 3, 4}; int intarray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; float floatarray[10] = {1.0d, 2.5d, -5.25d}; double dubarray[10] = {1.0d, 2.0d, -5.25d, 4.0d, 5.0d, 6.0d, 7.0d, 8.0d, 9.0d, 10.0d}; void main() { /* Integer Types*/ bool containsInt = array_contains(3, intarray, sizeof(intarray)); bool containsShort = array_contains(3, shortarray, sizeof(shortarray)); /* Floating Point Types*/ // elem_size not needed, as macro calls sizeof(float/double) bool containsFloat = array_contains(2.5d, floatarray, sizeof(floatarray)); bool containsDouble = array_contains(-5.25d, dubarray, sizeof(dubarray)); /* Struct*/ ts_arr[0] = (example_struct_t){.a = 5, .b = 2.5f}; ts_arr[1] = (example_struct_t){.a = 5, .b = -2.5f}; ts_arr[2] = (example_struct_t){.a = 3, .b = 2.5f}; ts_arr[3] = (example_struct_t){.a = 4, .b = 1.4f}; example_struct_t ts = (example_struct_t){.a = 3, .b = 2.5f}; //Unfortuantely we still need to cast the struct to a char* (All pointers must be cast) bool containsStruct = array_contains((char*)&ts, ts_arr, sizeof(ts_arr)); } 
float arr[] = ...; 

declares an array(because of []) of floats(because of the float keyword) that is called arr. Similarly in a function declaration:

int valueinarray(float val, float *arr[]); 

means that the second argument is a pointer(because of *) to an array(because of[]) which isn't what you need at all. You need to accept just an array:

int valueinarray(float val, float arr[]); 

folowing this logic your code would look like this:

int valueinarray(float val, float arr[]) { int i; for(i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { if(arr[i] == val) return 1; } return 0; } 

Notice a number of changes:

  1. The function parameter arr is now an array.

  2. The expression

    sizeof(arr) / sizeof(arr[0]) first takes the size of arr the array in bytes. Then divides it by the size in bytes of the first element in it. And now we are left with the number of elements. If it were a char array, then this is not needed is 1 char takes up 1 byte. For any other type it is necessary. As a side note you could've divided by sizeof(float), but this makes it harder for you if you change the type and is a worse practice than sizeof(arr[0]).

If you didn't divide you could have an array of 10 floats, but you could try to access 40. So you'd be meddling with 120 bytes of the memory right after your array that aren't yours.

Also the asterisk * dereferences outside of declarations. If you have:

int a[5]; int x = 5; int *pointerToInt = &x; 

then x is a label for some memory (variable) that stores the value 5. &x is a pointer to the memory that stores the label. The opposite of & is *. Whlie pointerToInt equals &x, *pointerToInt equals x. Also *a equals a[0], but isn't literally the same, *(a+1) equals a[1].

4

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like