Varargs in c and Objective-C

Variadic arguments in c, and how to use them in Objective-C

2018

With varargs, as their name suggests (var-args), you can define a function or method that takes a variable number of arguments. This was most commonly used in Objective-C with NSArray’s initWithObjects and NSDictionary’s initWithObjectsAndKeys initializer methods prior to the introduction of array and dictionary literals. While no longer as popular, it is still a useful tool particularly when the use of a literal is not the best option.

Use in c

Your favourite function in c may be a variadic one, certainly if that function is printf() or one its siblings like snprintf(). They are some of the most commonly used functions in the standard c library.

Includes

To use varargs yourself, first you must include stdarg.h.

#include <stdarg.h>

Note: the older varargs.h is deprecated. Do not use it unless you must.

Definition

Defining a variadic function can be super simple, but can make the implementation difficult if not impossible. In this example, how are you going to determine how many arguments have been provided?

double average(double first, ...);

It is almost always beneficial to include a count, or some way to indicate how many additional arguments you are providing.

double average(size_t count, ...);

Sometimes its also beneficial to manually include the first argument particularly when you want to ensure at least one argument is given or hint to a user what type is expected.

double average(size_t count, double first, ...);

Implementation

The implementation should roughly follow this pattern.

retType function(size_t count, ...)
{
	// ...
	va_list list;
	va_start(list, count);
	// ...
	// call `va_arg(list, someType)` for each argument.
	// ...
	va_end(list);
	// ...
	return reVal;
}

An example of averaging doubles could look as follows.

double average(size_t count, double first, ...)
{
	double sum = first;

	va_list list;
	va_start(list, first);

	size_t remaining = count;
	while (remaining > 1)
	{
		sum += va_arg(list, double);
		--remaining;
	}

	va_end(list);

	return sum / count;
}

Note: The first argument is used in the definition of sum. The loop will never see the first argument as it starts on the argument after the first argument.

Use in Objective-C

Includes

As in c, you must include stdarg.h. Though as this is Objective-C you should use #import.

#import  <stdarg.h>

Interface

Objective-C is a much more dynamic language than c and as a result it is common to find the use of nil terminated varargs lists. A naive approach to an interface would look something like this.

- (instancetype)initWithObjects:(ObjectType)firstObj, ...;

This creates room for mistakes. What if you forget to terminate the arguments with a nil? You could loop forever! More realistically, it would loop until you found a random nil on the stack. Fortunately GGC and Clang provide an __attribute__ to add compile-time detection of the missing nil. They call it a sentinel.

- (instancetype)initWithObjects:(ObjectType)firstObj, ... __attribute__((sentinel));

The Foundation framework goes another step further and conveniently wraps the sentinel attribute with the macro NS_REQUIRES_NIL_TERMINATION. An Example of its use would be NSArray’s initWithObjects which is defined as follows.

@interface NSArray : NSObject
// ...
- (instancetype)initWithObjects:(ObjectType)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
// ...
@end

If you are using the nil terminated approach you should always include it in your @interface definition.

Gotcha’s

Type-Safety

There is none. The compiler will not raise any compile-time errors for the use of a wrong type. The compiler doesn’t know what type you are expecting.

This is not exactly true of printf-like functions. There are additional __attributes__ that some compilers support. Clang and GCC accept something like the following.

Clang:

int myPrintf(const char *format, ...) __attribute__((__format__ (__printf__, 1, 2)))

GCC:

int myPrintf(const char *format, ...) __attribute__((format(printf, 1, 2)))

Note: In these examples ‘1’ is the index of the format argument, and the ‘2’ is the index of the first variadic argument.

On macOS there is a macro __printflike(fmtarg, firstvararg) which makes this easier.

int myPrintf(const char *format, ...) __printflike(1, 2)

Chars, Shorts, and Floats

Use of chars, shorts, and floats as the second argument of va_arg() is not portable. C ‘promotes’ char and short to int and float to double.

Further Resources

Licence

Any source in this article is released under the ISC License.