 As we discussed earlier, a void pointer is a generic kind of pointer, such that we can assign any kind of pointer to a void pointer without having to use a cast. Just like with the other pointers, we can create pointers to these pointers, so we can have a pointer to pointer to void. So here we have a void pointer variable terry, and a void pointer variable dara, and we assign the reference of terry to dara. Because again, pointers of all types are just addresses, we can convert pointers of any type to pointers of any other type with a cast, But again, the special thing about void pointers of any degree is that we need to cast other pointer types to assign the void pointer variables. So here we can assign dara to terry and vice versa without any casting, even though void pointer and void pointer are different types. The same is not true of non-void pointers, such as here when we must cast both terry and dara when we assign them to int pointer variable illsa. When going the other way, when assigning int pointer illsa to dara and terry, we don't need to cast, but the assignment of illsa to void pointer pointer dara will trigger a compiler warning unless we make the cast explicit. For some reason, the C99 standard decided that implicit casts to void pointers are okay, but implicit casts to void pointer pointers are too error prone to pass without a warning. It's very important in C to distinguish between what the language calls definitions and what it calls declarations. The distinction is quite confusing for three reasons. First, the choice of terms is totally arbitrary. It would have been just as logical of the two terms swapped meanings. Second, all definitions serve double duty as declarations, but a declaration is not necessarily a definition. Third, in common parlance, the terms are used very sloppily. In particular, it is very common to say variable declaration when we should say variable definition. In fact, I made the same mistake in earlier parts of this video. In any case, the distinction between definition and declaration is that a definition actually creates a variable or a function, but a declaration merely states the type associated with a name. For example, a function definition specifies a function's name, parameter names and types, the return type, and provides a body in curly braces because the definition actually creates a function. A function declaration, on the other hand, specifies the name, the parameter types, and the return type, but not the parameter names or a body. You actually can specify parameter names in a function declaration, but they are ignored by the compiler. So here, for example, we have a function definition that creates a function foo, which returns a void, and takes two parameters, an int called x and a font called y, and of course, to create a function, we must provide a body. In contrast, the declaration of the same function omits the body, and so instead ends with a semicolon. An important difference between definitions and declarations is that a variable or function may only be defined once, but may be declared as many times as needed. So the next question is, why do we need declarations? Well, to generate code that uses a name, the compiler needs to know the type associated with that name, and declarations provide this information. In many cases, the definition of a variable or function suffices as it's only needed declaration, but there are two situations where this is not the case. First, the code of a C program is generally split into separate source files that are compiled separately. If code in one source file needs to use a name defined in another source file, that name must be declared in the first source file. Second, C compilers strictly read source code files top to bottom, such that declarations of a name must proceed any use of that name. In many cases, you could rearrange the order of your definitions within a source file to satisfy this requirement, but this won't work if the file includes recursive functions. Besides, having to put our definitions in a particular order is bothersome, so a common practice is to put declarations of every function at the top of the file whether they are strictly needed or not. So consider a few examples. Assuming this shows the entirety of a source file, this call to bar triggers a compile error because no declaration of bar precedes the call. We can fix this by simply switching the order of the definitions. Because the definition of bar also counts as a declaration of bar, the compiler sees a declaration of bar before the call to bar. If we don't feel like reordering our definitions, a bothersome thing to do, we could simply add a declaration of bar in the function where it is called. As long as this declaration precedes any call to bar in the function, the compiler is happy. Putting ad hoc function declarations in your code like this is not a very common thing to do, however. The more common and general solution is to put a declaration of every defined function at the top of the source file. This way we can put our function definitions in any order without upsetting the compiler. Here's how you should actually think about the function declaration syntaxed. Just like asterisk is a modifier that makes a type a pointer and just like square brackets are a modifier that makes a type an array, the parentheses which contain a list of parameters are a modifier which makes a type a function. This is confusing because parens are also used to subvert order of precedence. The distinction is that the parens of a parameter list are a post-fix operator just like the square brackets. These parens come after what they modify rather than surround what they modify. Also like the square brackets, these parens have a higher precedence than asterisk. Here we have a declaration of a function called foo that takes two parameters and returns an int. As we just discussed, the way to read the syntax is to read the modifiers on the name in order of precedence. The parameter list is the only modifier, so foo is a function taking an int and char and returning an int. In the second example, the empty parameter list has higher precedence than asterisk, so foo is a function taking no arguments and returning a pointer to int. In this third example, the empty parameter list again has higher precedence than asterisk and thanks to the surrounding parens, the asterisk modifier is applied before the square brackets and so this is a function taking no arguments and returning a pointer to an array of 7 ints. Be clear that these three examples are declarations but not definitions. Each declares a function taking some set of parameters and returning some type. It's also important to understand that not all possible combinations of these modifiers are valid. Reading the modifiers inside out as usual, this example declares a function foo that takes no arguments and returns an array of 7 pointers to pointers to int. But this declaration is invalid because arrays are not values like ints or pointers and so a function may not return an array. Again, reading the modifiers inside out, this example declares an array of functions taking no arguments and returning pointers to ints but this declaration is also invalid because there's no such thing as an array of functions. What we can create however are pointers to functions. Here, reading inside out, foo is a pointer to a function taking no arguments and returning an int. And because we can create arrays of pointers, we can create arrays of pointers to functions. Here foo is an array of 6 pointers to a function taking no arguments and returning int. But again, there's no such thing as an array of functions and so we can't create pointers to arrays of functions. So you're probably wondering what is a pointer to a function, beyond of course just another memory address. Well it turns out that function names are always actually function pointer values. So when we invoke functions in C, we are actually invoking function pointers. Much like in say JavaScript where you can invoke as a function any expression which evaluates into a function object, in C you can invoke as a function any expression which evaluates into a function pointer. Here for example, if we have some defined function foo which takes no arguments and returns an int, then we can assign foo to a variable p which is a pointer to a function taking no arguments and returning foo. We then can invoke the function via the variable p just as we could via the function name itself. Be clear that the argument and return types of a function are integral to the pointer's type. So if the p variable were a pointer to a function taking a char argument and returning int, it would not be a valid assignment target for foo. Like an array name, a function name is a value not a variable. This means that function names cannot be reassigned. Also referencing a pointer to a function does not return a pointer to a pointer to a function as you might expect, but rather simply returns the same pointer value. So here assigning the reference of the function name foo to p does the same thing as simply assigning the name foo itself to p. To be honest, I'm not sure why referencing a function name doesn't simply trigger a compilation error. It's not as if referencing function names does something useful. Another strange thing about function pointers is that like with arrays, there's no such thing as a function value. So dereferencing a function pointer cannot return a function. Instead, dereferencing a function pointer value simply returns the same function pointer value. Here the dereference of the function name foo returns the same function pointer value represented by the name. When we dereference p, we get the same function pointer value which we can then invoke like any other function pointer value. Like with referencing function names, I'm not sure why dereferencing function pointers is allowed because it doesn't do anything useful. Now it is possible to get a function pointer pointer value by referencing a function pointer variable. Here the reference of variable p returns a function pointer pointer. To invoke functions, however, we may only use function pointers themselves, not function pointer pointers. So we cannot invoke function pointer pointer variable p2, but we can invoke the dereference of p2. Note that parenz around the dereference of p2 here are very necessary because without them the argument list parenz would have higher precedence and so this expression would be the dereference of the value returned by a call to p2 which is not only the wrong syntax but would be an invalid operation because we can't invoke p2 as a function. In a few contexts, we need to specify pointer types without having any name to modify. Here to cast the value of this variable foo into an int pointer pointer pointer there is no name for the asterisks to modify. In the next example, to cast a variable foo into a pointer to an array of 8chars we need to surround the asterisk in parentheses to subvert the precedence. Otherwise the cast type specified would be an array of 8chars pointers which is not only the wrong type but would make this cast totally invalid because you can't cast to arrays, arrays are not a kind of value. In this last example, rather than casting, we're declaring a function bar. This declaration states that bar takes as argument a pointer to a function which takes no arguments and returns int while bar itself returns float. Note that the parameter type declaration requires parentheses around the asterisk to subvert the higher precedence of the empty parameter list parens. In an earlier video, we discussed how natively compiled languages typically go through a two-step process. First, each source file is separately compiled into object code files and then these object code files get combined into a single executable by a program called a linker. Here's a simple example of how this works in C. Say we have two source files, one called main.c and another called foo.c. Both of these files are compiled separately and so when compiling main.c, the compiler has no knowledge about how the function foo is defined. However, main.c includes a declaration, a function foo with the same parameter types and return type and so the compiler can generate appropriate machine code for invoking foo. What's missing though is an actual address for execution to jump to when invoking foo. That gets filled in by the linker. The linker takes the two object files separately compiled from these two source files and from them produces a single executable. In the executable, there's an actual address for execution to jump to when invoking foo. By default, the executable produced by the linker will invoke the function called main to kick off execution of the program. So our compiled and linked program here will invoke main and end when main returns. Understand that C has no notion of namespacing. When the linker matches names from separate object files together, it does so in one global namespace. For example, the linker assumes that the function foo defined in one object file refers to the same function foo invoked in other object files because there is only one global namespace across all object files. This lack of namespaces is a holdover from an earlier era of programming but C programmers work around this deficiency with a simple convention. A library written in C by convention prefixes the names of everything it defines with an initialism or abbreviation of the library's name. The OpenGL library, for example, prefixes the names of what it defines with lowercasegl. While this prefixing convention adds clutter to C code, many C programmers insist they like it because it means that every name clearly states the library from which it comes. In the event of a name collision, such as when using two libraries that define something of the same name, you'll have to tell the linker how you wish to resolve the conflict.