Reputation: 195
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:
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?
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?
Upvotes: 12
Views: 112663
Reputation: 150
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[]);
However, in C this is equivalent to float *arr
- a pointer to some address in memory that we read floats from.
It doesn't guarantee that there are any floats actually saved there, nor can we know how many. You must keep track of that yourself. So the function should accept another argument - the size of the array. Here is Marco Bonelli's solution, which is correct:
bool valueinarray(float val, float *arr, size_t n) {
for(size_t i = 0; i < n; i++) {
if(arr[i] == val)
return true;
}
return false;
}
Upvotes: 1
Reputation: 69367
Using sizeof(*arr[])
is wrong, furthermore using sizeof(arr)
is also wrong. You cannot determine the size of an array passed as parameter in C. This information is only known to the caller. You will have to also pass the size (or number of elements) as an additional argument.
Additionally, you should use size_t
for i
, and since you are effectively returning a boolean result (either 0
or 1
) you can use bool
as the return type (since C99) available in the stdbool.h
header, or _Bool
(for C89).
Here's the correct implementation taking the number of elements as third argument:
bool valueinarray(float val, float *arr, size_t n) {
for(size_t i = 0; i < n; i++) {
if(arr[i] == val)
return true;
}
return false;
}
Or, if you want, you can pass the size of the array instead and make a simple division to determine the number of elements:
bool valueinarray(float val, float *arr, size_t size) {
for(size_t i = 0; i < size / sizeof(*arr); i++) {
if(arr[i] == val)
return true;
}
return false;
}
Finally, note that due to floating point math being imprecise, when dealing with floating point values such as float
and double
, you should avoid strict equality checks. See How should I do floating point comparison? for more information
Upvotes: 3
Reputation: 137
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));
}
Upvotes: 0