I think it would help you the most to know just what is going on in the compiler's head. Using your example:
typedef struct {
int lengthInSeconds;
int yearRecorded;
} Song;
There are actually two things happening here. The first is that you're defining an anonymous struct
:
struct {
int lengthInSeconds;
int yearRecorded;
}
This anonymous struct
can be used anywhere a type (e.g. int
or const char*
) could be used. The following example is actually valid, though nobody should ever write code like this because it's a maintenance nightmare:
struct {
int lengthInSeconds;
int yearRecorded;
}* GetSongInfo(const char* songName);
This declares a function that gets the information for a song with a given name (assuming names are unique, an invalid assumption in real life) and returns a pointer to a struct
with its metadata.
I referred to that as an "anonymous struct
" because the named struct form looks like this:
struct Song {
int lengthInSeconds;
int yearRecorded;
}
Now you can refer to that struct by name, but not in the fashion you would think coming from languages other than C! Specifically, the function above would now be declared as:
struct Song* GetSongInfo(const char* songName);
Note that the type name is struct Song
and not just Song
! As a matter of fact, struct
s have a separate namespace of their own... so there is nothing in the language to guarantee that struct Song
and Song
are the same thing! I have no idea if Objective C maintains this insanity, but I'd bet it does to be on the safe side.
The second thing is that you're feeding that anonymous structure to typedef
. typedef
simply takes one type and defines a name for it that you can use later. So you're really saying "a Song
is an alias for that struct {...}
I just declared."
By typedef
ing Song
, you can instead write:
Song* GetSongInfo(const char* songName);
(Yes, I know it would be better to return const Song*
, but that's beside the point.)
It may interest you to note that C has many typedefs in its standard libraries, intended to separate the particulars of a platform from the particulars of the C standard. For example, <unistd.h>
defines size_t
, the integer type that represents a size (and is the return type of the built-in sizeof()
function), and by extension, the type you should pass to functions like malloc()
. There is also the recent <stdint.h>
header which defines integer types by their size in bits, e.g. uint8_t
for an 8-bit unsigned integer. The line in <stdint.h>
probably looks something like:
typedef unsigned char uint8_t;
Since most platforms have 8-bit characters. (I've programmed for some platforms like TI DSPs where a char
was not 8-bits; usually it was 16, since the processor could not access parts of words. But let's ignore degenerate platforms for the time being.)
Finally, to explain your examples:
Song myvariable;
This declares storage for a Song
structure. If it is in a function definition, that storage will be allocated on the stack and released when the function terminates. If it is outside a function definition, it will be allocated as a global. In either case, it will be uninitialized, which in C means it can contain any random values. C does not keep track of uninitialized variables or set them to "uninitialized" or whatever like PHP and Python does. C doesn't throw exceptions either.
Song myfunction(int params) { }
This defines a function that takes an integer and returns a Song
structure. The return
statement for the function must be given a Song
. For example:
Song myfunction(int params)
{
Song result;
result.lengthInSeconds = GetSongLength(params);
result.yearRecorded = GetSongYear(params);
return result;
}
This function allocates space on the stack for a Song
, fills in its fields by calling functions that return int
s, then return
copies the structure result
into the space reserved for the return value, and finally the storage for result
is released when the function ends. Usually, the compiler can optimize away the copy implicit in the return
statement. Also, there may be another (possibly optimized) copy from the return value storage to a final destination, for example when you say:
Song foundSongInfo = myfunction(params);
By the way, returning struct
s on the stack is frowned upon, since compilers can usually optimize better if the return value fits in a register (i.e. is a simple type). (There are exceptions in C++ with operator overloading, but that's off-topic.) A better definition would be:
void myfunction(int params, Song* found)
{
found->lengthInSeconds = GetSongLength(params);
found->yearRecorded = GetSongYear(params);
}
This avoids all the extra copies. You call it like so:
Song foundSongInfo;
myfunction(params, &foundSongInfo);
This is one case where two lines can be faster than one.
I may have missed some things, let me know if this makes sense...