Copying Arrays in C: How to Assign One List to Another List

Arrays in C programming language are fundamental data structures that store multiple elements of the same data type in contiguous memory locations. Copying arrays is a crucial operation in programming, enabling developers to create independent data copies, preserve array states, pass data between functions, and implement various algorithms efficiently. This article explores different array copying techniques in C, discussing their advantages, disadvantages, and best practices for writing robust and efficient code.

Below are some of the methods to create a copy of a given array in C programming language.

Using a Loop (Element-by-Element Copy)

The element-by-element copy method is a straightforward approach to copying arrays in C. This technique involves iterating through each element of the source array and copying it to the corresponding position in the destination array.

Algorithm

  1. Declare and allocate memory for the destination array.
  2. Use a loop to iterate through each element of the source array.
  3. Copy each element from the source array to the destination array.

Code Example

Here’s an example code on how to create a copy of a given array using a loop in C programming language:


#include <stdio.h>
#include <stdlib.h>  // Required for malloc and free functions

// Define a constant for the size of our arrays
#define SIZE 5

// Function to copy elements from one array to another
void copyArray(int *src, int *dest, int size) {
  // Loop through each element of the arrays
  for (int i = 0; i < size; i++) {
    // Copy the i-th element from source to destination
    dest[i] = src[i];
  }
}

int main() {
  // Allocate memory for the source array
  int *source = (int *)malloc(SIZE * sizeof(int));
  if (source == NULL) {
    printf("Memory allocation failed for source array\n");
    return 1;  // Exit if memory allocation fails
  }

  // Initialize the source array with some values
  for (int i = 0; i < SIZE; i++) {
    source[i] = i + 1;  // Filling with values 1, 2, 3, 4, 5
  }

  // Allocate memory for the destination array
  int *destination = (int *)malloc(SIZE * sizeof(int));
  if (destination == NULL) {
    printf("Memory allocation failed for destination array\n");
    free(source);  // Free the previously allocated memory
    return 1;  // Exit if memory allocation fails
  }

  // Call the copyArray function to copy elements from source to destination
  copyArray(source, destination, SIZE);

  // Print the copied array to verify the result
  printf("Copied array: ");
  for (int i = 0; i < SIZE; i++) {
    printf("%d ", destination[i]);
  }
  printf("\n");  // Print a newline for better formatting

  // Free the dynamically allocated memory
  free(source);
  free(destination);

  return 0;  // Indicate successful program execution
}

Time and space complexity

  • Time complexity: O(n), where n is the number of elements in the array.
  • Space complexity: O(1) for the copying process itself (excluding the space for the destination array).

Using memcpy() Function

The memcpy() function is a powerful and efficient way to copy arrays in C. It’s part of the <string.h> library and is designed to copy a block of memory from one location to another.

Introduction to memcpy()

memcpy() stands for “memory copy”. It copies a specified number of bytes from a source memory area to a destination memory area. This function is not limited to arrays; it can copy any contiguous block of memory.

How to use memcpy() for array copying

To use memcpy(), you need to provide three arguments:

  1. Pointer to the destination array
  2. Pointer to the source array
  3. Number of bytes to copy

The function prototype is:

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

Code Example

Here’s an example of how to use memcpy() to copy an integer array:


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

// Define a constant for the size of our arrays
#define SIZE 5

int main() {
    // Dynamically allocate memory for the source array
    int *source = (int *)malloc(SIZE * sizeof(int));
    if (source == NULL) {
        printf("Memory allocation failed for source array.\n");
        return 1;
    }

    // Initialize the source array with 5 integers
    for (int i = 0; i < SIZE; i++) {
        source[i] = i + 1;  // This will set values 1, 2, 3, 4, 5
    }

    // Dynamically allocate memory for the destination array
    int *destination = (int *)malloc(SIZE * sizeof(int));
    if (destination == NULL) {
        printf("Memory allocation failed for destination array.\n");
        free(source);  // Free the previously allocated memory before exiting
        return 1;
    }

    // Copy the contents of the source array to the destination array
    // Parameters:
    //   1. destination: where we're copying to
    //   2. source: what we're copying from
    //   3. SIZE * sizeof(int): total number of bytes to copy
    memcpy(destination, source, SIZE * sizeof(int));

    // Print the copied array to verify the operation
    printf("Copied array: ");
    
    // Loop through each element of the destination array
    for (int i = 0; i < SIZE; i++) {
        // Print each element followed by a space
        printf("%d ", destination[i]);
    }
    
    // Print a newline character to end the output cleanly
    printf("\n");

    // Free the dynamically allocated memory
    free(source);
    free(destination);

    // Return 0 to indicate successful program execution
    return 0;
}

Time and space complexity

  • Time complexity: O(n), where n is the number of bytes to be copied.
  • Space complexity: O(1) for the copying process itself (excluding the space for the destination array).

memcpy() is generally faster than a manual loop-based copy, especially for large arrays, as it’s often optimized at the hardware level.

Using strcpy() Function (for character arrays)

The strcpy() function, defined in the <string.h> library, is specifically designed for copying null-terminated strings (character arrays) in C. strcpy() copies characters from the source string to the destination string until it encounters a null terminator (\0). It then adds a null terminator to the destination string.

Syntax of strcpy()

To use strcpy(), you need to provide two arguments:

  1. Pointer to the destination string
  2. Pointer to the source string

The function prototype is:

char *strcpy(char *dest, const char *src);

Code Example

Here’s an example of how to use strcpy() to copy a string:


#include <stdio.h>
#include <string.h>
#include <stdlib.h>  // Include stdlib.h for malloc and free functions

// Define a constant for the maximum length of strings
#define MAX_LENGTH 100

int main() {
    // Declare and initialize the source string
    char source[] = "Hello, World!";
    
    // Declare a pointer for the destination and allocate memory
    char* destination = (char*)malloc(MAX_LENGTH * sizeof(char));

    // Check if memory allocation was successful
    if (destination == NULL) {
        printf("Memory allocation failed. Exiting program.\n");
        return 1;  // Return 1 to indicate an error
    }

    // Copy the contents of the source string to the destination array
    // strcpy() is a standard library function that copies strings
    strcpy(destination, source);

    // Print the copied string to verify the operation
    // %s is the format specifier for strings in printf()
    printf("Copied string: %s\n", destination);

    // Free the allocated memory
    free(destination);

    // Return 0 to indicate successful program execution
    return 0;
}

Time and space complexity

  • Time complexity: O(n), where n is the length of the source string.
  • Space complexity: O(1) for the copying process itself (excluding the space for the destination array).

While strcpy() is convenient for string copying, it’s generally recommended to use strncpy() or other safer alternatives to prevent buffer overflow issues.

Using Assignment Operator (Shallow Copy)

The assignment operator (=) in C can be used to create a shallow copy of an array. However, this method has significant limitations and potential issues that developers should be aware of.

Explanation of shallow copy

A shallow copy creates a new array variable that points to the same memory location as the original array. This means that both arrays share the same data, and changes made to one array will affect the other.

Limitations and potential issues

  • Data Sharing: Both arrays reference the same memory location, leading to unintended modifications.
  • Memory Management: Freeing memory for one array can lead to dangling pointers for the other.

Code Example


#include <stdio.h>

int main() {
  // Declare and initialize the original array
  int original[] = {1, 2, 3, 4, 5};
  
  // Create a shallow copy of the original array
  // Note: This doesn't create a new array, it just points to the same memory location
  int *copy = original;

  // Print the copy array before modification
  printf("Copy array: ");
  for (int i = 0; i < 5; i++) {
    printf("%d ", copy[i]);
  }
  printf("\n");

  // Modify an element in the copy
  // Since it's a shallow copy, this will affect the original array as well
  copy[2] = 99;

  // Print the original array to see the changes
  printf("Original array after copy array is modified: \n");
  
  // Loop through each element of the original array
  for (int i = 0; i < 5; i++) {
    // Print each element with a space separator
    printf("%d ", original[i]);
  }
  
  // Print a newline character to end the output line
  printf("\n");

  // Exit the program successfully
  return 0;
}

When to use and when to avoid

Use shallow copy when:

  • You need a temporary reference to an array without creating a new copy.
  • You want to pass an array to a function that won’t modify its contents.
  • Working with read-only data where multiple references are acceptable.

Avoid shallow copy when:

  • You need to modify the copied array independently of the original.
  • Dealing with dynamically allocated memory that might be freed.
  • Working with multi-threaded applications where data races could occur.
  • You need a true independent copy of the array data.

In most cases, it’s safer and more predictable to use deep copy methods like memcpy() or element-by-element copying to create truly independent array copies.

Deciding on which method to use

When choosing an array copying method in C, consider the following factors:

  • Array Type: Choose your method based on the array’s data type. For integer or floating-point arrays, memcpy() or element-by-element copying are suitable options. When dealing with character arrays (strings), opt for strcpy() or the safer strncpy() function.

  • Performance: Consider the size of your array when selecting a method. For small arrays, any copying method will perform adequately. However, when working with large arrays, memcpy() generally offers superior performance due to its low-level optimizations.

  • Flexibility: If you need to apply custom logic during the copying process, element-by-element copying provides the most flexibility. On the other hand, if you’re performing a straightforward, bulk copy operation, memcpy() is the most efficient choice.

  • Safety: Each method has different safety considerations. memcpy() requires careful size calculations to prevent buffer overflows. Element-by-element copying can be safer if implemented correctly with proper bounds checking. For string operations, strncpy() is safer than strcpy() as it allows you to specify a maximum length, reducing the risk of buffer overflows.

  • Portability: If you’re aiming for maximum portability across different systems and compilers, element-by-element copying is your best bet. While memcpy() and strcpy() are widely supported, they may not be available in all environments, particularly in embedded systems or non-standard C implementations.

  • Memory Alignment: When dealing with memory alignment issues, memcpy() is generally safe for all data types and memory alignments. However, manual element-by-element copying might need to consider alignment for certain data types on some architectures, especially when working with low-level or embedded systems.

  • Partial Copying: If your task involves copying only a portion of an array, you have two suitable options. You can use element-by-element copying with custom start and end indices, or employ memcpy() with carefully calculated offsets and sizes.

  • Code Readability: The choice between methods can affect how easy your code is to understand. memcpy() often results in more concise code for simple copying tasks, which can enhance readability. However, element-by-element copying can be more explicit and easier to understand for beginners or when the copying logic is complex.

In general, memcpy() is the preferred method for most array copying tasks due to its efficiency and simplicity. However, always consider the specific requirements of your project and the nature of the data you’re working with when choosing a copying method.