Concatenating Arrays

Yet another tool missing from C, but found in other programming languages, is the capability to stick together two arrays. The result is a single, larger array containing the elements from the two originals. With C, of course, you can always code your own function to handle the task.

The strcat() function does array concatenation, gluing together two character arrays — but only character arrays (strings). Beyond strings, an array concatenation function must know the array’s data type. This tidbit requires either separate, data type specific functions or that the data type be passed as an argument to a generic array concatenation function.

Another required knowledge tidbit is the array’s element count. A string is terminated with the null character, but any other type of array has no guaranteed final element value. As the arrays are referenced by address (pointer) within the function, you can’t use the sizeof keyword to determine the number of elements: In a function, sizeof returns the pointer’s size, not the array size.

Here is my intarraycat() function, which concatenates two integer arrays:

/* concatenate two integer arrays */
int *intarraycat(int *a, int asize, int *b, int bsize)
{
    int *c,x;

    /* allocate storage for new array */
    c = malloc( sizeof(int) * (asize+bsize) );

    /* concatenate upon success */
    if( c!=NULL )
    {
        /* copy first array */
        for( x=0; x<asize; x++ )
            *(c+x) = *(a+x);
        /* copy second array */
        for( x=0; x<bsize; x++ )
            *(c+x+asize) = *(b+x);
    }

    return(c);
}

The four arguments are two pairs, the arrays and their number of elements. Variable c is allocated to store both array’s elements, using the two size values to set the storage size. Upon success, the if statement block executes. Otherwise, NULL is returned, which is tested for in the main() function.

Two for loops in the function assign array elements. Pointer notation is used as the array data is passed as a pointer and referenced in variable c.

In the second for loop, the offset for buffer c is calculated by using both looping variable x and the size of the first array, asize. This expression ensures that the elements in new array c are contiguous.

The function returns pointer c, which references the buffer containing the combined arrays.

The main() function declares two integer arrays and assigns them values, outputs each array, then calls the intarraycat() function in these statements:

    /* concatenate and output */
    cat = intarraycat(a,as,b,bs);
    if( cat!=NULL )
    {
        printf("Result: ");
        for( x=0; x<as+bs; x++ )
            printf(" %d",*(cat+x));
        putchar('\n');
    }
    else
    {
        puts("Concatenation error");
    }

Integer pointer cat captures the return value from the intarraycat() function. If the value is not NULL, a for loop outputs the new concatenated array. Otherwise, an error message is output. Variables a and b represent the two original integer arrays; as and bs are the array sizes. Click here to view the full code. Here is sample output:

Array 'a': 11 22 33 44 55
Array 'b': 89 78 67 56 45 1
Result: 11 22 33 44 55 89 78 67 56 45 1

I suppose a more graceful way to concatenate two arrays might exist. Then again, in other programming languages, the coders need not worry about elegant coding because such a function already exists. In C, we must try harder.

5 thoughts on “Concatenating Arrays

  1. Do you think it would be possible to make a general purpose function which takes a type or type size as a sort of primitive generics? Also, I think you could probably use memcopy.

  2. You might be able to write a generic function by using the typeof operator, which will be introduced with C23. I don’t have any details, but it might work. Otherwise you could just memcpy() the raw data, but you must still know the size of the data chunk – which means it’s easier to just to do it the way I did it and write a separate function for each data type. *sigh*

  3. Intrigued by Chris Webbʼs question, I tried to rewrite the above solution so that all the actual heavy lifting could be relegated to memmove()/memcpy():

    ~/ArrayCat$ cat include/arrayutil.h
    void *genarraycat (size_t elemsz, void *a, size_t alen, void *b, size_t blen);
    #define arraycat(a, alen, b, blen) \
    genarraycat(sizeof(a[0]), a, alen, b, blen) /* assumes typeof(*a)==typeof(*b) */
    ~/ArrayCat$ cat src/arrayutil.c
    void *genarraycat (size_t elemsz, void *a, size_t alen, void *b, size_t blen) {
    size_t asize = alen * elemsize;
    size_t bsize = blen * elemsize;
    void *c = malloc (asize + bsize); /* allocate storage for new array */
    if (c != NULL) { /* concatenate upon success */
    memcpy (c, a, asize);
    memcpy ((char *)c + asize, b, bsize);
    }
    return (c);
    }

    Invoking the compiler with -O3, the results are as follows (on Ubuntu 22.04.1):

    * While actual timings vary, memcpy() is usually faster at 10 elements (at worst)

    * However, those additional 2 function calls do cost. And if requested to do so, modern compilers are able to generate *very* efficient code… while the expected speed up (from using memcpy) usually ranges from 10 to 30%, at times it can even be slower.

    * memcpy()ʼs parameters, as opposed to memmove(), are restrict-qualified, so one would expect some (moderate) performance gains. Interestingly, on Linux this is not what one sees—sometimes even the reverse is true; on Windows, however, memcpy() usually seems to do its work about 2-3% faster than memmove().

    I posted more detailed timings on GitHub (should anyone be interested): https://tinyurl.com/4a65xtx7

  4. I’m glad you took the time to follow-up with code on that. Bingo on the compiler optimization. In fact, I would be tempted (if I only had the time) to code the memory copying routine in assembly just because I’m nerdy and I’d enjoy seeing how it worked.

  5. For a moment, I too thought about coding it up in assembly; then I stepped through memcpy.asm (in VS2022/Windows) a bit, and decided that it already used every conceivable trick under the sun (from SSE, AVX detection to non-temporal loads), so itʼd probably not be worth my time…

Leave a Reply