Variadic functions are a powerful feature in C that allow you to create functions capable of handling a variable number of arguments. This capability makes your code more flexible and adaptable to different situations.
- Introduction to Variadic Functions
- Basic Examples
- Intermediate Examples
- Advanced Examples
- Example 11: Dynamic SQL Query Builder
- Example 12: Configurable Command Processor
- Example 13: Parsing Complex Input
- Example 14: Dynamic Memory Allocation
- Example 15: Multi-Type Data Processing
- Example 16: Custom Data Structure Handling
- Example 17: Flexible Error Handling
- Example 18: Implementing a Dispatcher
- Example 19: Multi-Threaded Logging System
- Example 20: Building a Complex Mathematical Expression
- Best Practices
- Conclusion
- Further Reading
A variadic function is a function that accepts a variable number of arguments. This is achieved by including an ellipsis (...
) in the function's declaration. The most common example of a variadic function is printf
, which can accept a varying number of arguments based on the format string.
To handle these arguments, variadic functions use a set of macros provided by the <stdarg.h>
header file:
va_list
: A type that holds the information needed to retrieve additional arguments.va_start
: Initializes ava_list
variable to start retrieving arguments.va_arg
: Retrieves the next argument in the list.va_end
: Cleans up theva_list
variable after all arguments have been processed.
- Logging and Debugging: Variadic functions allow the creation of flexible logging systems where you can pass a variable number of messages or data points.
- Data Processing: Functions that process arrays, lists, or sets of data can use variadic functions to handle different amounts of input data.
- Custom Implementations of Standard Functions: For example, implementing your own version of
printf
,scanf
, etc.
This example demonstrates how to sum a variable number of integers:
#include <stdarg.h>
#include <stdio.h>
void sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
printf("Sum: %d\n", total);
}
int main() {
sum(3, 1, 2, 3); // Output: Sum: 6
sum(5, 10, 20, 30, 40, 50); // Output: Sum: 150
return 0;
}
A variadic function that prints each argument:
#include <stdarg.h>
#include <stdio.h>
void print_args(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
int arg = va_arg(args, int);
printf("Argument %d: %d\n", i + 1, arg);
}
va_end(args);
}
int main() {
print_args(3, 10, 20, 30);
return 0;
}
This function finds the maximum value among the arguments:
#include <stdarg.h>
#include <stdio.h>
int find_max(int count, ...) {
va_list args;
va_start(args, count);
int max = va_arg(args, int);
for (int i = 1; i < count; i++) {
int value = va_arg(args, int);
if (value > max) {
max = value;
}
}
va_end(args);
return max;
}
int main() {
printf("Max: %d\n", find_max(4, 10, 20, 5, 15)); // Output: Max: 20
return 0;
}
Similar to finding the maximum, this function finds the minimum value:
#include <stdarg.h>
#include <stdio.h>
int find_min(int count, ...) {
va_list args;
va_start(args, count);
int min = va_arg(args, int);
for (int i = 1; i < count; i++) {
int value = va_arg(args, int);
if (value < min) {
min = value;
}
}
va_end(args);
return min;
}
int main() {
printf("Min: %d\n", find_min(4, 10, 20, 5, 15)); // Output: Min: 5
return 0;
}
This function concatenates multiple strings into a single string:
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
void concat_strings(char *result, int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
strcat(result, va_arg(args, char *));
}
va_end(args);
}
int main() {
char result[100] = "";
concat_strings(result, 3, "Hello, ", "World", "!");
printf("Result: %s\n", result); // Output: Result: Hello, World!
return 0;
}
This custom logging function can log messages with varying arguments:
#include <stdarg.h>
#include <stdio.h>
void log_message(const char *format, ...) {
va_list args;
va_start(args, format);
printf("LOG: ");
vprintf(format, args);
va_end(args);
}
int main() {
log_message("This is a log message: %d, %s\n", 42, "hello");
return 0;
}
A function that formats a string using a format specifier:
#include <stdarg.h>
#include <stdio.h>
void format_string(char *result, const char *format, ...) {
va_list args;
va_start(args, format);
vsprintf(result, format, args);
va_end(args);
}
int main() {
char result[100];
format_string(result, "Name: %s, Age: %d", "John Doe", 30);
printf("Formatted String: %s\n", result); // Output: Formatted String: Name: John Doe, Age: 30
return 0;
}
This function sums an array of integers of any size:
#include <stdarg.h>
#include <stdio.h>
int sum_array(int count, ...) {
va_list args;
va_start(args, count);
int sum = 0;
for (int i = 0; i < count; i++) {
sum += va_arg(args, int);
}
va_end
(args);
return sum;
}
int main() {
int result = sum_array(5, 1, 2, 3, 4, 5);
printf("Sum of array: %d\n", result); // Output: Sum of array: 15
return 0;
}
This function calculates the average of a set of numbers:
#include <stdarg.h>
#include <stdio.h>
double calculate_average(int count, ...) {
va_list args;
va_start(args, count);
double sum = 0.0;
for (int i = 0; i < count; i++) {
sum += va_arg(args, int);
}
va_end(args);
return sum / count;
}
int main() {
double avg = calculate_average(4, 10, 20, 30, 40);
printf("Average: %.2f\n", avg); // Output: Average: 25.00
return 0;
}
This function demonstrates how to handle multiple data types using variadic functions:
#include <stdarg.h>
#include <stdio.h>
void print_values(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
char type = va_arg(args, int); // Retrieve the type specifier
if (type == 'i') {
int val = va_arg(args, int);
printf("Integer: %d\n", val);
} else if (type == 'f') {
double val = va_arg(args, double);
printf("Float: %f\n", val);
} else if (type == 'c') {
char val = (char)va_arg(args, int); // char is promoted to int
printf("Char: %c\n", val);
} else {
printf("Unknown type\n");
}
}
va_end(args);
}
int main() {
print_values(3, 'i', 42, 'f', 3.14, 'c', 'A');
return 0;
}
This function builds a dynamic SQL query using variadic arguments:
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
void build_query(char *query, const char *table, int count, ...) {
va_list args;
va_start(args, count);
strcpy(query, "SELECT * FROM ");
strcat(query, table);
strcat(query, " WHERE ");
for (int i = 0; i < count; i++) {
const char *column = va_arg(args, const char *);
const char *value = va_arg(args, const char *);
strcat(query, column);
strcat(query, " = '");
strcat(query, value);
strcat(query, "'");
if (i < count - 1) {
strcat(query, " AND ");
}
}
va_end(args);
}
int main() {
char query[256];
build_query(query, "users", 2, "name", "John Doe", "age", "30");
printf("SQL Query: %s\n", query); // Output: SQL Query: SELECT * FROM users WHERE name = 'John Doe' AND age = '30'
return 0;
}
A function that processes commands with a variable number of arguments:
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
void process_command(const char *command, int count, ...) {
va_list args;
va_start(args, count);
printf("Processing command: %s\n", command);
for (int i = 0; i < count; i++) {
char *arg = va_arg(args, char *);
printf("Argument %d: %s\n", i + 1, arg);
}
va_end(args);
}
int main() {
process_command("COPY", 2, "file1.txt", "file2.txt");
process_command("MOVE", 3, "file1.txt", "file2.txt", "file3.txt");
return 0;
}
This example demonstrates how to parse complex input using variadic functions:
#include <stdarg.h>
#include <stdio.h>
void parse_input(const char *format, ...) {
va_list args;
va_start(args, format);
while (*format != '\0') {
if (*format == 'd') {
int i = va_arg(args, int);
printf("Integer: %d\n", i);
} else if (*format == 'c') {
int c = va_arg(args, int); // char is promoted to int
printf("Char: %c\n", c);
} else if (*format == 'f') {
double d = va_arg(args, double);
printf("Float: %f\n", d);
}
++format;
}
va_end(args);
}
int main() {
parse_input("dcf", 42, 'A', 3.14);
return 0;
}
A variadic function that allocates memory dynamically based on input types:
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
void *allocate_memory(const char *format, ...) {
va_list args;
va_start(args, format);
void *memory = NULL;
while (*format != '\0') {
if (*format == 'i') {
memory = malloc(sizeof(int));
int value = va_arg(args, int);
*(int *)memory = value;
printf("Allocated memory for int: %d\n", *(int *)memory);
} else if (*format == 'f') {
memory = malloc(sizeof(double));
double value = va_arg(args, double);
*(double *)memory = value;
printf("Allocated memory for double: %f\n", *(double *)memory);
}
++format;
}
va_end(args);
return memory;
}
int main() {
int *pInt = (int *)allocate_memory("i", 100);
double *pDouble = (double *)allocate_memory("f", 3.14);
free(pInt);
free(pDouble);
return 0;
}
This function processes different types of data passed as arguments:
#include <stdarg.h>
#include <stdio.h>
void process_data(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
char type = va_arg(args, int); // Retrieve the type specifier
if (type == 'i') {
int val = va_arg(args, int);
printf("Processing integer: %d\n", val);
} else if (type == 'f') {
double val = va_arg(args, double);
printf("Processing float: %f\n", val);
} else if (type == 's') {
char *val = va_arg(args, char *);
printf("Processing string: %s\n", val);
}
}
va_end(args);
}
int main() {
process_data(3, 'i', 42, 'f', 3.14, 's', "Hello, World!");
return 0;
}
This example shows how to handle custom data structures:
#include <stdarg.h>
#include <stdio.h>
typedef struct {
int id;
char name[50];
} Person;
void print_person_info(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
Person *person = va_arg(args, Person *);
printf("Person ID: %d, Name: %s\n", person->id, person->name);
}
va_end(args);
}
int main() {
Person p1 = {1, "John Doe"};
Person p2 = {2, "Jane Smith"};
print_person_info(2, &p1, &p2);
return 0;
}
A function that handles errors based on a variable number of error messages:
#include <stdarg.h>
#include <stdio.h>
void handle_errors(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
char *error_message = va_arg(args, char *);
printf("Error: %s\n", error_message);
}
va_end(args);
}
int main() {
handle_errors(3, "File not found", "Memory allocation failed", "Invalid input");
return 0;
}
A function dispatcher that calls different functions based on the command:
#include <stdarg.h>
#include <stdio.h>
void command_dispatcher(const char *command, ...) {
va_list args;
va_start(args, command);
if (strcmp(command, "PRINT") == 0) {
char *message = va_arg(args, char *);
printf("PRINT: %s
\n", message);
} else if (strcmp(command, "SUM") == 0) {
int a = va_arg(args, int);
int b = va_arg(args, int);
printf("SUM: %d\n", a + b);
} else {
printf("Unknown command: %s\n", command);
}
va_end(args);
}
int main() {
command_dispatcher("PRINT", "Hello, World!");
command_dispatcher("SUM", 10, 20);
return 0;
}
A more complex example of logging in a multi-threaded environment:
#include <stdarg.h>
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
void thread_safe_log(const char *format, ...) {
va_list args;
va_start(args, format);
pthread_mutex_lock(&log_mutex);
vprintf(format, args);
pthread_mutex_unlock(&log_mutex);
va_end(args);
}
void *thread_function(void *arg) {
thread_safe_log("Thread %d: %s\n", (int)arg, "Logging from thread");
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, thread_function, (void *)1);
pthread_create(&thread2, NULL, thread_function, (void *)2);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
}
This advanced example demonstrates how to build and evaluate a complex mathematical expression:
#include <stdarg.h>
#include <stdio.h>
double evaluate_expression(int count, ...) {
va_list args;
va_start(args, count);
double result = 0;
for (int i = 0; i < count; i++) {
char op = va_arg(args, int); // Operation
double value = va_arg(args, double); // Value
if (op == '+') {
result += value;
} else if (op == '-') {
result -= value;
} else if (op == '*') {
result *= value;
} else if (op == '/') {
result /= value;
}
}
va_end(args);
return result;
}
int main() {
double result = evaluate_expression(5, '+', 10.0, '*', 3.0, '-', 5.0, '/', 2.0);
printf("Result: %.2f\n", result); // Output: Result: 25.00
return 0;
}
- Type Safety: Always be mindful of the types of arguments when using
va_arg
. Incorrect types can lead to undefined behavior. - Documentation: Clearly document the expected argument types and order.
- Error Handling: Implement error checks to handle invalid or unexpected arguments gracefully.
- Avoid Overuse: Use variadic functions when necessary, but avoid them if a fixed argument function would be clearer or more maintainable.
Variadic functions in C provide powerful flexibility for handling functions that require a variable number of arguments. From basic summation functions to complex command processors and mathematical expression evaluators, variadic functions enable you to write more dynamic and reusable code.