我一直在尝试提高自己在C语言中的技能和知识。今天,我尝试创建一个可以接受任何类型数组的函数,但是我没有找到成功的方法,我正在使用ANSIC。我试图将其作为void指针传递,但是当我尝试使用参数操作内存时,编译器会抱怨。有什么办法可以实现?我当时想可能可以通过预处理程序指令来完成,但我不确定。
PS:我的目标不是用数据填充数组,而只是一个函数,而是了解和学习如何在不知道数据类型的情况下传递数据,或者让我的函数不仅仅用于处理数据一种数据。
这是编译过程的输出:
array_test.c:在“ array_fill”函数中:
array_test.c:34:13:警告:算术[-Wpointer-arith]中使用的'void *'类型的指针
*(array + i)=数据;
^
array_test.c:34:5:警告:取消引用“ void *”指针
*(array + i)=数据;
^ ~~~~~~~~~~~
array_test.c:34:5:错误:无效使用void表达式
*(array + i)=数据;
^
这是我的代码:
#include <stdio.h>
#define array_length(array) (sizeof(array)/sizeof(array[0]))
#define ARBITRARY_SIZE 10
typedef enum
{
false,
true
} bool;
int array_fill(void*, int, int);
int main(int argc, char* argv[])
{
int array[ARBITRARY_SIZE];
int i;
array_fill(array, array_length(array), 0);
for(i = 0; i < array_length(array); i++)
{
printf("array[%d]: %d\n", i, *(array + i));
}
return 0;
}
int array_fill(void* array, int size, int data)
{
int i;
for(i = 0; i < size; i++)
{
*(array + i) = data;
}
/*I will implement a check later, in case of errors.*/
return 0;
}
答案 0 :(得分:4)
指针指向内存中某个对象的开头。 大多数指针还通过类型知道内存中该对象的大小,例外是void *
。
例如如果指向32位整数的指针的值为0,则我们知道位0到31包含与该32位整数相对应的数据。
0 31
|---| <- 32 bits storing the data for a 32-bit integer
对于您的问题更重要的是,如果我们知道该指针指向32位整数序列,那么我们知道可以通过将指针向前移动32位来获得下一个整数。例如。第二个整数将从32开始。
0 31 32 63
|---| |---|
This is what int[2]. might look like in memory on a 32-bit system
这是指针算术的工作方式。使用空白指针void *array
时,您将无法执行array++
甚至*array
,因为无法知道要前进多少位或对应于array
的位数。
0 ??
|----
We don't know how many bits a void pointer points to
您也可以通过传递对象的大小来技术上来解决这个问题,尽管这可能不是一个好主意。
// array points to the memory to be filled
// len is the number of elements in the array
// size is the size of an element (in bytes)
// fill points to an object to be used to fill array
void array_fill(void* array, int len, size_t size, void* fill) {
// char is always a single byte
char* byte_ptr = (char*) array;
for (int i = 0; i < len; i++) {
// Fill the current element
memcpy(byte_ptr, fill, size);
// Advance byte_ptr the correct number of bytes
byte_ptr += size;
}
}
如果不想使用memcpy
,也可以一次将fill
对象一次复制到byte_ptr
一个字节。
答案 1 :(得分:2)
这里的问题有两个。首先是解引用void
指针,另一个是对其进行算术运算。正如您在帖子中所显示的,编译器还会警告您。
您不能直接添加到void
指针,因为编译器不知道添加地址时需要走多远,它需要指向的对象的大小,才能做吧。因此,您需要先将虚空类型转换为具体内容,然后才能向其中添加内容。
类似地,您不能取消引用空指针,因为同样,编译器也不知道要提取多少字节,因为void
没有任何隐含的长度。
答案 2 :(得分:1)
没有类型,array
引用的数据没有元素大小,因此指针算术未定义。为了使表达式有效,必须将array
强制转换为适当的数据类型,例如:
*((int*)array + i) = data;
但是,这违反了具有未定义类型的目的。最简单,最有效的解决方案是为要填充的每种类型的数组定义单独的函数。可以定义一个函数来处理多种 integer 类型:
int array_fill(void* array, size_t array_length, long long data, size_t data_size )
{
if( data_size > sizeof(data) )
{
data_size = sizeof(data) ;
}
for( size_t i = 0; i < array_length; i++)
{
for( int b = 0; b < data_size; b++ )
{
((char*)array)[i * data_size + b] = (data >> (b * 8)) & 0xff ;
}
}
return 0;
}
上面有两个假设:
char
类型。如果这些假设不正确,则必须进行修改。请注意,我还使用了数组索引符号,而不是指针算术-它导致更少的括号,因此更易于阅读。
然后可以在您的情况下调用该函数,例如:
array_fill( array, array_length(array), 0, sizeof(*array) ) ;
和array
可以是任何类型。
但是用零填充数组是一种特殊情况,不需要这种复杂性(即,在您的示例用法中,它没有任何作用)。以下:
memset( array, sizeof(array), 0 ) ;
具有相同的效果,无论如何,某些整数0的所有字节均为零。对于每个字节不同的值,该功能更有用。
array_fill( array, array_length(array), 0x01234ABCD, sizeof(*array) ) ;
现在,如果array
的类型为uint8_t
,则它将用0xCD
填充,如果是uint16_t
,则0xABCD
。如果它是long long
,并且在目标是64位类型上,则将用0x0000000001234ABCD
填充。
如果有些麻烦,也可以使用此函数来填充float
或double
数组,例如:
double array[ARBITRARY_SIZE];
double x = 0.5 ;
array_fill(array, ARBITRARY_SIZE, *(long long*)(&x), sizeof(array) );
另一种允许使用聚合类型甚至任意长度序列作为填充的方法是:
int array_fill( void* array, size_t array_length,
const void* fill_pattern, size_t fill_pattern_length )
{
for( size_t i = 0; i < array_length; i++)
{
for( int b = 0; b < fill_pattern_length; b++ )
{
((char*)array)[i * fill_pattern_length + b] = ((char*)fill_pattern)[b] ;
}
}
return 0;
}
然后它可以真正用于任何类型。例子:
Double
double array[ARBITRARY_SIZE], x = 0.5 ;
array_fill( array, ARBITRARY_SIZE, &x, sizeof(x) );
int
int array[ARBITRARY_SIZE], x = 123456 ;
array_fill( array, ARBITRARY_SIZE, &x, sizeof(x) );
结构
struct S{ int x; double f ; } array[ARBITRARY_SIZE], x = {1234, 0.5};
array_fill( array, ARBITRARY_SIZE, &x, sizeof(x) );
2D阵列
int array[ARBITRARY_SIZE][2], x[2] = { 12, 98 } ;
array_fill( array, ARBITRARY_SIZE, &x, sizeof(x) );
该实现避免了字节序问题,但由于您无法使用地址,因此不能接受常量常量初始化程序。
可以改进(简化并提高效率)最后一个实现;例如:
int array_fill( void* array, size_t array_length,
const void* fill_pattern, size_t fill_pattern_length )
{
for( size_t i = 0, byte_index = 0;
i < array_length;
i++, byte_index += fill_pattern_length )
{
memcpy( &((char*)array)[byte_index], fill_pattern, fill_pattern_length ) ;
}
return 0;
}
这就是我要使用的版本。
答案 3 :(得分:1)
太像@Increasingly Idiotic的好答案。
因此,我将制作此Wiki。有用作为参考。
有什么方法可以将未知类型的数组作为参数传递给C语言中的函数?
是的,代码可以使用数组调用此类函数,但是该数组将转换为数组的第一个元素的地址。函数将使用该地址。
some_type a[N];
foo(a);
要使函数接受任何数组对象类型,函数参数为void *
。
int foo(void *address_of_first_element);
很遗憾,foo()
丢失了类型。
有什么方法可以实现?
在OP的情况下,array_fill()
仅需要类型的大小,而不需要类型本身。因此,传入类型的大小。
OP看到需要数组大小并通过它-很好。还需要元素的大小和填充值的指针。
要执行指针数学运算,请将void*
转换为char *
,因为void*
上的指针数学运算不是C定义的。
// int array_fill(void* array, int size, int data)
int array_fill(void* array, size_t a_size, const char *fill, size_t e_size) {
char *data = array;
for(size_t a = 0; a < a_size; a++) {
memcpy(data, fill, e_size); // Copy `e_size` bytes.
data += e_size; // Advance `e_size` bytes.
}
return 0;
}
int main(void) {
int array[ARBITRARY_SIZE], fill_value = 42;
array_fill(array, array_length(array), &fill_value, sizeof array[0]);
for(size_t i = 0; i < array_length(array); i++) {
printf("array[%zu]: %d\n", i, *(array + i));
}
return 0;
}
答案 4 :(得分:0)
如果您希望用数据填充类型的数组,那么可以说是值是2.2的double数组,甚至是结构{int a;任何b};那么基本上答案是“不”,你不能这样子。
您可以为此使用宏,例如
void fill_array(void * array, size_t item_size, size_t array_len, void (*cb)(void *))
{
unsigned char *bytes = array;
for (size_t i = 0; i < array_len; i++) {
cb(&bytes[i * item_size]);
}
}
void fill_double(void *data)
{
const value = 2.2;
double *ptr = *data;
*data = value;
}
int main(void)
{
double array[30];
fill_array(array, sizeof double, 30, fill_double);
}
但这不是函数。
但是您可以创建一个函数,该函数接受一个能够分配数据的回调,例如:
def main():
args = docopt(__doc__, version = __version__)
img_filter = Filter(args['<curves>'], 'crgb')
im = Image.open(args['<filepath>'])
image_array = np.array(im)
filter_manager.add_filter(img_filter)
filter_array = filter_manager.apply_filter('crgb', image_array)
im = Image.fromarray(filter_array)
output_name = '%s_%s.png' % (get_name(args['<filepath>']), get_name(args['<curves>']))
im.save(output_name)
不确定这样做是否值得,但是应该看起来像是您的问题的解决方案(未编译,可能包含错误)