Next article: Friday Q&A 2011-03-04: A Tour of OSAtomic
Previous article: Complete Friday Q&A Direct-Sell ePub, PDF, and Print on Demand
Tags: c99 fridayqna
We're back to our regular schedule at last. For today's edition, I'm taking a break from the usual reader-driven format to discuss a topic of my own choosing: compound literals in C99.
Compound literals are a relatively unknown feature in C. They are fairly new. Introduced as part of the C99 standard in 2000, they've been around for a while, but for a language that dates to the 1960s, it's a recent addition.
C99 added a lot of useful features to the language that modern Mac and iOS programmers tend to take for granted. Many of these existed as compiler extensions beforehand. Simple features like //
comments, the long long
type, and the ability to mix variable declarations and code are all new in C99. Compound literals are much less well known than these features, but are equally standard and can be handy, and this is why I want to talk about them today.
Compound Literal Basics
Compound literals provide a way to write values of arbitrary data types in code. An expression like "hello"
is a string literal that has type char *
. A compound literal is simply a different kind of expression that has whatever type you're after. For example, it's possible to create an expression which produces the same C string as the string literal, but with explicit character-by-character construction:
(char []){ 'h', 'e', 'l', 'l', 'o', '\0' }
(NSSize){ 1, 2 }
NSMakeSize(1, 2)
but without the need for an external function. Similar syntax will work for any type, even custom-defined structs.
Compound literal syntax closely matches variable initialization syntax. For example:
NSSize s = { 1, 2 };
(NSSize){ 1, 2 }; // same value
int x[] = { 3, 4, 5 };
(int []){ 3, 4, 5 }; // same
Type name = { val };
(Type){ val };
int
) don't require {}
to be initialized, but {}
is still required to create a compound literal. It is not the same to write (int)3
and (int){ 3 }
, although they act similarly in many cases. The former simply takes the integer constant 3
and uselessly casts it to int
, whereas the latter is essentially a variable declaration with no name.
Basic Tricks
The ability to create custom struct values is probably the most useful obvious application of compound literals. Although Cocoa takes care of its most common types with NSMakeRect
and friends, there are still places to put compound literals to good use.
For example, a CGRect
is really just an CGPoint
and an CGSize
. CGRectMake
takes four discrete numbers, but sometimes it's more convenient to just deal with those two elements. Compound literals let you do that inline:
[layer setFrame: (CGRect){ origin, size }];
[NSString stringWithCharacters: (unichar []){ 0x00a9 } length: 1]
A compound literal is essentially an anonymous variable declaration and initialization. As such, it follows the same scoping rules as regular variables. For example, this is perfectly legal:
int *ptr;
ptr = (int []){ 42 };
NSLog(@"%d", *ptr);
NSLog
executes, so it is legal to dereference the pointer. This, however, is not legal:
int *ptr;
do {
ptr = (int []){ 42 };
} while(0);
NSLog(@"%d", *ptr);
do
/while
loop, and it no longer exists afterwards. The NSLog
statement may print junk or crash.
Mutability
One really unintuitive thing about compound literals is that, unless you declare their type as const
, they produce mutable values. The following is perfectly legal, albeit completely pointless, code:
(int){ 0 } = 42;
For example, a common operation on sockets is to set the SO_REUSEADDR
option. This tells the OS to free up the socket's port for use as soon as the socket is closed, instead of the default behavior of waiting a few minutes first. This option is set using setsockopt
. It can be used to set various parameters which need different argument types, so it simply takes a void *
and a length. This is how it's normally used to set this option:
int yes = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int));
Another place where this comes in handy is writing methods which take an NSError **
parameter to pass error information to the caller. By convention, it's legal to pass NULL
as the pointer to indicate that the caller doesn't care about the error. This means that at every place where an error can occur, the pointer must be checked. This gets a bit tedious:
- (BOOL)doWithError: (NSError **)error
{
if(fail1)
{
if(error)
*error = [NSError ...];
return NO;
}
if(fail2)
{
if(error)
*error = [NSError ...];
return NO;
}
if(fail3)
{
if(error)
*error = [NSError ...];
return NO;
}
return YES;
}
- (BOOL)doWithError: (NSError **)error
{
error = error ? error : &(NSError *){ nil };
if(fail1)
{
*error = [NSError ...];
return NO;
}
if(fail2)
{
*error = [NSError ...];
return NO;
}
if(fail3)
{
*error = [NSError ...];
return NO;
}
return YES;
}
NULL
, but that generally wouldn't matter, and the result is somewhat more readable. It also allows the method to call other error-returning methods in a natural way and make use of the result before returning the error to the caller:
- (BOOL)doWithError: (NSError **)error
{
error = error ? error : &(NSError *){ nil };
BOOL success = [obj doWithError: error];
if(!success)
{
// don't bail out if we can work around it
if(![[*error domain] isEqual: CanWorkAroundDomain])
return NO;
}
if(fail1)
{
*error = [NSError ...];
return NO;
}
return YES;
}
I discussed using compound literals and macros a bit in my post on C macros, but it's useful enough that it bears repeating. By using a compound literal to create an array, you can easily create a macro which takes variable arguments and then does something useful with them. As an example, this macro makes it simpler to create
NSArray
objects:
#define ARRAY(...) [NSArray \
arrayWithObjects: (id []){ __VA_ARGS__ } \
count: sizeof((id []){ __VA_ARGS__ }) / sizeof(id)]
id []
syntax with compound literals, and by using sizeof
on the resulting array, you can create macros which do useful things with an arbitrary number of arguments.
Conclusion
Compound literals are a nice trick to simplify and clarify code. They are not universally applicable, and you must take care not to use them in situations where they hurt more than they help. However, they are a nice tool to have in your bag of tricks, and they help make C a little more useful and generic.
That's it for this time. I hope to be back to my regular schedule now, so look for another post in two weeks. Until then keep sending your ideas. Friday Q&A is (usually!) driven by reader suggestions, so if you have a topic that you would like to see covered here, send it to me.
Comments:
(this time it was the mutability of the literals, that they're actually like anonymous variables. Thanks!)
if(!error)
error = &(NSError *){ nil };
wouldn't the literal become invalid after the scope of the if clause is left, thus resulting in a dangling pointer just like in the "Scope" case above?
Johannes Fortmann: If statements don't actually create a new scope unless you also include explicit
{}
after it. It's safe to take the address of compound literals (and make blocks without copying them) inside a bare if.int myarray[3] = { [1] = 3 }; //results in {0, 3, 0}
beautiful when you're declaring something at file scope and using enum constants for indices, because it means no more counting the lines and then comparing that to the enum values
Then again, I tested with 'struct T { int i; }' rather than NSError*, maybe there's a difference there somewhere?
Tommi: The subject of if statements and scopes came up on this blog in the comments to this post:
http://www.mikeash.com/pyblog/friday-qa-2010-01-15-stack-and-heap-objects-in-objective-c.html
I had originally stated that the if statement had its own scope, and the comments said otherwise. However, now that I actually look it up, I believe you (and my original position) are right. From my copy of a draft C standard:
The term "selection statement" covers if, if/else, and switch.
Although it seems that gcc and clang will let you get away with this construct, to be completely safe you should probably write it using the conditional operator instead:
error = error ? error : &(NSError *){ nil };
Or using the gcc extension that lets you omit the middle:
error = error ?: &(NSError *){ nil };
error ?= &(NSError *){ nil };
But yeah, I love the field initialization syntax that Jonathan and Jared brought up.
In an ideal world, every developer should know every detail about every language or API they use. But in practice, I keep forgetting stuff I don't use regularily.
I have seen this argument pop up before and to me it sounds like an excuse not to learn. If we can't use things that most people don't use regularly, then there is no room for change or growth.
Well, if I see
[layer setFrame: (CGRect){ origin, size }];
then it's pretty clear that a rectangle is being constructed. If I'm not sure, then a quick click on setFrame: in Xcode will tell me.
As a potential influencer of C-writing youth, however, this post is a bull in a china shop.
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int));
is neither "cleaner" nor "more readable" than the two-step alternative. The two-step example tells a maintainer what '1' means to the called function! And sure, reuseaddr might be an easy-to-remember case, but what about o_nonblock?
Your example with the NSError initializer creates a function that no-ops silently (and probably leads to a memory leak) instead of failing conspicuously. If it's supposed to output an NSError but the incoming output location is NULL, you could scarcely do worse than creating a local, fake output address.
Compound literals have a place in the world, but if I were interviewing someone and they tried to tell me how great this compound literal style is, and referenced these examples, I would consider it a mercy to yell at them before asking them to get the hell out of my office.
&(int){ YES }
be more to your liking? I see no difference between declaring a temporary variable to hold the value and doing so for any function or method which takes an int by value. Do you declare temporary variables in all of those cases as well?
Regarding NSError, you seem to have deeply misunderstood how the whole system works. It is the standard Cocoa convention that you can pass NULL to an NSError ** parameter if you don't care about the particulars of the error. Success or failure is still signaled by use of the return value. If the caller passel NULL, you are still supposed to indicate success or failure, and you are not supposed to fail just because you pass NULL. I have no idea why you think there would be a memory leak here. I can't even begin to correct it without knowing why you think there would be, but suffice it to say that there is not.
No offense, but if you were interviewing me and you started going on about silent failures and memory leaks in a situation where they clearly do not apply, I would consider it a stroke of luck to not get the job.
(And yeah, there's no leak. Obviously.)
(CGRect){ x, y, w, h}
instead of
(CGRect){{x, y}, {w, h}}
So you can "flatten" the struct.
Hopefully they behave the same.
int *ptr;
do {
ptr = (int []){ 42 };
} while(0);
NSLog(@"%d", *ptr);
Neither doesn't crash nor prints junk value in Xcode 5. It just prints 42. How could it work like this?
Comments RSS feed for this page
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.
someView.frame = (CGRect){ .size = someSize };
Any member you don't specify is zero-ed out for you.