简体   繁体   中英

Concatenate two arrays using void pointer (C)

I want to concatenate two arrays of the same type into a single new array with the same type. But the problem is I have to use void pointers, and somehow my code won't work from the third element on. I searched a bit on the internet but seems like noone is having this problem

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void array_float_fill(float *arr1, float *arr2, int n, int m){
    for(int i = 0;i<n;i++){
        *(arr1+i)=i+n;
    }
    for(int i = 0;i<m;i++){
        *(arr2+i)=i+m;
    }
}

void array_concat(void *arr1, void *arr2, void *arr3, int n, int m,int size){
    for(int i=0;i<n;i++){
        memcpy((arr3+i),(arr1+i),size); 
    }   
    for(int i=0;i<m;i++){
        memcpy((arr3+i+n),(arr2+i),size);
    }
}

int main(int argc, char const *argv[])
{
    int n=10;
    int m=10;
    float f1[n];
    float f2[m];
    array_float_fill(f1,f2,n,m);
    printf("%f",*(f1+3));
    float* f3 = malloc((n+m) * sizeof(float));
    array_concat(f1,f2,f3,n,m,sizeof(float));
    printf("%f",*(f3+3));
    return 0; 
}

I tried it with a for -loop to copy every single element to the new array, because the function will just give me a pointer to the start of the array. Dunno why it doesn't work. Any help and hints would be much appreciated

void* pointer arithmetic ...

... is not actually allowed in standard C. gcc allows it and assumes a base operand size of 1. Thus it follows

Index assumption

You are confusing bytes with indexes when using memcpy .

When (non-standardly) adding integer to void pointers, the integer's meaning (in gcc) is that of a byte-offset. This is unlike a typed pointer, there the compiler will do the appropriate scaling for you:

void *p;
int i;
p[i];   // This will be literally p+i after compiling on your compiler

But for example:

float *p;
int i;
p[i];   // This will be p+i*sizeof(float) after compiling.

So instead of ...

for(int i=0;i<n;i++){
    memcpy((arr3+i),(arr1+i),size); 
}   
for(int i=0;i<m;i++){
    memcpy((arr3+i+n),(arr2+i),size);
}

... you got to write (this is still compiler-specific):

for(int i=0;i<n;i++){
    memcpy((arr3+i*size), (arr1+i*size), size);
}
for(int i=0;i<m;i++){
    memcpy((arr3+i*size+n*size), (arr2+i*size), size);
}

... or go standards-conforming:

for(int i=0;i<n;i++){
    memcpy((char*)arr3+i*size, (char*)arr1+i*size, size);
}
for(int i=0;i<m;i++){
    memcpy((char*)arr3+i*size+n*size, (char*)arr2+i*size, size);
}

By conversion to char* , you enforce a base operand size of 1 byte (more precisely, 1 C byte), which is what you want given the generic nature of your concat function.

However

Note that you can pass arbitrary strides to memcpy , you do not actually need the loops; instead, just do:

memcpy((char*)arr3, arr1, size*n);
memcpy((char*)arr3+n*size, arr2, size*m);

Conversions are only needed where you do arithmetics.

You don't have to memcpy in a loop. If you know the size and the length of the arrays you only need two:

void array_concat(void *arr1, void *arr2, void *arr3, int n, int m,int size)
{
    memcpy( arr3 , arr1 , size * n ) ;
    memcpy( ( char* )arr3+( size * n ) , arr2 , size * m ) ;
}

The line ( char* )arr3+( size * n ) gives a pointer to the end of the first part.

The cast to char* is needed because pointer arithmetic doesn't work on void*, so we manually increment the pointer to the correct offset.

For example ( char* )arr3+( n ) would not be correct because the underlying type is float. That is why you pass the size of float and then use it.

Your compiler seems to have an extension that allows you to use void* as it were a char* when using pointer arithmetic.Then you increment it incorrectly here: arr3+i just by the value of
sizeof( char ),instead of sizeof( float ).

You can copy an entire array with a single memcpy . Check the signature of memcpy :

void *memcpy(void *dest, const void *src, size_t n);

It will copy n contiguous bytes from src to dest , as simple as it sounds. An array is a contiguous block of memory, so you can copy an entire array using a single call to memcpy .

I fixed this and a few other things in the code below:

  • Access arrays by indices rather than doing pointer arithmetic, which is easy to get wrong.

  • Constness

  • Usage of size_t rather than int for sizes and array indices. See Why size_t matters .

  • It's a good habit to pass the output buffer size and check it prior to any copying in order to prevent buffer overflows.

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void array_float_fill(float *arr1, float *arr2, size_t n, size_t m) {
    for (size_t i = 0; i<n; i++) {
        arr1[i] = i + n;
    }
    for (size_t i = 0; i<m; i++) {
        arr2[i] = i + m;
    }
}

void array_concat(
    void *out_arr,
    size_t out_arr_size,
    const void *in_arr1,
    size_t in_arr1_size,
    const void *in_arr2,
    size_t in_arr2_size)
{
    const size_t total_size = in_arr1_size + in_arr2_size;
    // BE AWARE that `assert` will be gone if you define `NDEBUG`
    // (or compile with `-DNDEBUG`).
    // This assertion guarantees we have enough space before copying.
    assert(out_arr_size >= total_size);
    memcpy(out_arr,                        in_arr1, in_arr1_size);
    memcpy((char *)out_arr + in_arr1_size, in_arr2, in_arr2_size);
    // The cast to `char *` above is important for portability.
    // Arithmetic on a pointer to void is a GNU extension.
    // See http://gcc.gnu.org/onlinedocs/gcc/Pointer-Arith.html
}

void dump_array(const char *name, float *array, size_t num_elems) {
    printf("%s = ", name);
    for (size_t i = 0; i < num_elems; i++) {
        printf("%f ", array[i]);
    }
    printf("\n");
}

int main() {
    const size_t n = 10;
    const size_t m = 10;
    float f1[n];
    float f2[m];

    array_float_fill(f1, f2, n, m);
    const size_t f3_size = (n + m) * sizeof(float);
    float *f3 = malloc(f3_size);
    array_concat(f3, f3_size, f1, sizeof(f1), f2, sizeof(f2));

    // Show the results
    dump_array("f1", f1, n);
    dump_array("f2", f2, m);
    dump_array("f3", f3, n + m);

    return 0; 
}

Arithmetic operations cannot be performed on void pointers. Since void pointers don't have any fixed data type like int or float, thus they don't know with what value they should get incremented or decrement upon arithmetic operations. For example, when you increment integer pointer, the value is automatically increased by 4 upon every increment. Similarly character pointer will be incremented by 1 upon every increment. Here in your example, when memcpy operation trying to copy the value from arr1 to arr3, it can't be able to de-reference arr3, hence the problem. Generally whenever any such operations performed on void pointers, first they need to be de-referenced. De-reference it to floating pointer will work, see below

 memcpy(((float*)arr3+i),((float*)arr1+i),size);

 memcpy(((float*)arr3+i+n),((float*)arr2+i),size);

Try the following

void array_concat( const void *arr1, const void *arr2, void *arr3, int n, int  m, int size )
{
    memcpy( arr3, arr1, n * size );

    memcpy( ( char * )arr3 + n * size, arr2, m * size );  
}

Here is how the program can look

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void array_float_fill( float *arr1, float *arr2, size_t n, size_t m )
{
    for ( size_t i = 0; i < n; i++ )
    {
        *( arr1 + i ) = i + n;
    }

    for ( size_t i = 0; i < m; i++ )
    {
        *( arr2 + i ) = i + m;
    }
}

void array_concat( const void *arr1, const void *arr2, void *arr3, 
                   size_t n, size_t  m, size_t size )
{
    memcpy( arr3, arr1, n * size );

    memcpy( ( char * )arr3 + n * size, arr2, m * size );  
}

int main( void )
{
    size_t n = 10;
    size_t m = 10;
    float f1[n];
    float f2[m];

    array_float_fill( f1, f2, n, m );

    printf( "%f\n",*( f1 + 3) );

    float *f3 = malloc( ( n + m ) * sizeof( float ) );

    array_concat( f1, f2, f3, n, m, sizeof( float ) );

    printf( "%f\n", *( f3 + 3 ) );

    free( f3 );

    return 0;
}

The output is

13.000000
13.000000

Do not forget to free the dynamically allocated array. Also you should include header <string.h> where memcpy is declared.

Take into account that this declaration of main

int main(int argc, char const *argv[])

is wrong. The correct declaration looks like

int main(int argc, char *argv[])

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM