【问题标题】:Concatenate two arrays using void pointer (C)使用 void 指针 (C) 连接两个数组
【发布时间】:2015-02-07 07:22:54
【问题描述】:

我想将两个相同类型的数组连接成一个具有相同类型的新数组。但问题是我必须使用void 指针,并且不知何故,我的代码从第三个元素开始就无法工作。我在互联网上搜索了一下,但似乎没有人遇到这个问题

#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; 
}

我尝试使用for-loop 将每个元素复制到新数组中,因为该函数只会给我一个指向数组开头的指针。不知道为什么它不起作用。任何帮助和提示将不胜感激

【问题讨论】:

  • 你需要#include &lt;string.h&gt;来获取memcpy的声明。

标签: c arrays function concatenation memcpy


【解决方案1】:

您可以使用单个memcpy 复制整个数组。检查memcpy的签名:

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

它会将n 的连续字节从src 复制到dest,听起来很简单。数组是一个连续的内存块,因此您可以通过一次调用 memcpy 来复制整个数组。

我在下面的代码中修复了这个问题和其他一些问题:

  • 通过索引访问数组而不是做指针运算,这很容易出错。

  • Constness

  • 使用size_t 而不是int 来表示大小和数组索引。请参阅 Why size_t matters

  • 传递输出缓冲区大小并在任何复制之前检查它以防止缓冲区溢出是一个好习惯。

#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; 
}

【讨论】:

    【解决方案2】:

    试试下面的

    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 );  
    }
    

    这是程序的外观

    #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;
    }
    

    输出是

    13.000000
    13.000000
    

    不要忘记释放动态分配的数组。您还应该在声明 memcpy 的地方包含标题 &lt;string.h&gt;

    考虑到这个 main 声明

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

    错了。 正确的声明看起来像

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

    【讨论】:

      【解决方案3】:

      不能对 void 指针执行算术运算。由于 void 指针没有任何像 int 或 float 这样的固定数据类型,因此它们不知道在算术运算时应该递增或递减什么值。例如,当您递增整数指针时,该值会在每次递增时自动增加 4。类似地,字符指针将在每次递增时递增 1。在您的示例中,当 memcpy 操作尝试将值从 arr1 复制到 arr3 时,它无法取消引用 arr3,因此出现了问题。通常,每当对 void 指针执行任何此类操作时,首先需要取消引用它们。取消对浮动指针的引用将起作用,见下文

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

      【讨论】:

        【解决方案4】:

        您不必循环使用 memcpy。如果你知道数组的大小和长度,你只需要两个:

        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 ) ;
        }
        

        ( char* )arr3+( size * n ) 行给出了一个指向第一部分结尾的指针。

        需要强制转换为 char*,因为指针运算不适用于 void*,因此我们手动将指针递增到正确的偏移量。

        例如( char* )arr3+( n ) 将不正确,因为基础类型是浮点数。这就是为什么你传递 float 的大小然后使用它的原因。

        您的编译器似乎有一个扩展,允许您在使用指针算术时使用 void*,因为它是一个 char*。然后您在这里错误地递增它:arr3+i 只是按
        sizeof(char) 的值,而不是 sizeof(float)。

        【讨论】:

        • 谢谢!似乎工作。 :) 你能解释一下为什么我可以像往常一样在这种情况下使用 memcpy() 吗?因为据我所知,我只是给了一个指向数组的指针,所以函数不能“看到”整个数组。
        • @Kuro95 编译器如何知道什么类型是 void?它没有,也不能增加它。问题不是 memcpy() 而是这个:arr3+i,其中 arr3 是 void*。
        • 啊,所以据我了解,这只是将字节 1to1 从 array-start + 任何大小复制到另一个数组,而不知道它的实际类型?
        • @Kuro95 如果你的意思是 memcpy 那么是的。
        【解决方案5】:

        void*指针算术...

        ... 在标准 C 中实际上是不允许的。gcc 允许它并假定基本操作数大小为 1。因此它遵循

        指数假设

        在使用memcpy 时,您会将字节与索引混淆。

        当(非标准)将整数添加到void 指针时,整数的含义(在 gcc 中)是字节偏移的含义。这与类型化指针不同,编译器会为您进行适当的缩放:

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

        但是例如:

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

        所以不是...

        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);
        }
        

        ...您必须编写(这仍然是特定于编译器的):

        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);
        }
        

        ...或符合标准:

        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);
        }
        

        通过转换为 char*,您强制执行 1 个字节(更准确地说,1 个 C 字节)的基本操作数大小,鉴于您的 concat 函数的通用性质,这是您想要的。

        然而

        请注意,您可以将任意步幅传递给memcpy,实际上并不需要循环;相反,只需这样做:

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

        只有在进行算术运算时才需要转换。

        【讨论】:

        • 那在Visual C中编译不出来,void指针元素的大小不是1,未知。
        猜你喜欢
        • 2023-03-04
        • 2017-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-07
        • 2011-05-16
        • 1970-01-01
        • 2020-04-01
        相关资源
        最近更新 更多