Consider a C program that manipulates some data in an unsafe way, a situation that generally leads to either a memory access violation at runtime or a silent data corruption. An access violation (sometimes just called an AV) happens when protected memory is written to by accident; this is generally more desirable (and debuggable) than blindly overwriting memory. This snippet of code clobbers the stack, meaning that the control flow of your program and various bits of data — including the return address for the current function — could be overwritten. It's bad:
#include <stdlib.h>
#include <stdio.h>
void fill_buffer(char*, int, char);
int main()
{
int x = 10;
char buffer[16];
/* ... */
fill_buffer(buffer, 32, 'a');
/* ... */
printf("%d", x);
}
void fill_buffer(char* buffer, int size, char c)
{
int i;
for (i = 0; i < size; i++)
{
buffer[i] = c;
}
}
Our main function allocates two items on its stack, an integer x and a 16-character array named buffer. It then passes a pointer to buffer (remember, it's on the stack), and the receiving function fill_buffer proceeds to use the size and character c parameters to fill the buffer with that character. Unfortunately, the main function passed 32 instead of 16, meaning that we'll be writing 32 char-sized pieces of data onto the stack, 16 more than we should have. The result can be disastrous. This situation might not be so bad depending on compiler optimizations — we could simply overwrite half of x — but could be horrific if we end up overwriting the return address. It is only possible because we are permitted to access raw memory entirely outside of the confines of C's primitive type system.
Static and Dynamic Typing
Type systems are often categorized using a single pivot: static versus dynamic. The reality is that type systems vary quite a bit more than being just one or the other. Nonetheless, the CTS provides capabilities for both, giving languages the responsibility of choosing how to expose the CLR's features. There are strong proponents of both styles, although many programmers feel most comfortable somewhere in the middle. Regardless of your favorite language, the CLR runs code in a strongly typed environment. This means that your language can avoid dealing with types at compile time, but ultimately it will end up having to work within the type system at runtime. Everything has a type, whether a language designer surfaces this to users or not.
Key Differences in Typing Strategies
Static typing seeks to prove program safety at compile time, thus eliminating a whole category of runtime failures to do with type mismatches and memory access violations. C# programs are mostly statically typed, although some features like casting enable you to relax or avoid static typing in favor of dynamism. In such cases, the runtime ensures types are compatible at runtime. Other examples of statically typed languages include Java, Haskell, Standard ML, and F#. C++ is very much like C# in that it uses a great deal of static typing, although there are several areas that can cause failures at runtime, notably in the area of type-unsafe memory manipulation, as is the case with old-style C.
Some people feel that static typing forces a more verbose and less explorative programming style. Type declarations are often littered throughout programs, for instance, even in cases where a more intelligent compiler could infer them. The benefit, of course, is finding more errors at compile time, but in some scenarios the restriction of having to play the "beat the compiler" game is simply too great. Dynamic languages defer to runtime many of the correctness checks that static languages perform at compile time. Some languages take extreme and defer all checks, while others employ a mixture of static and dynamic checking. Languages like VB, Python, Common LISP, Scheme, Perl, Ruby, and Python fall into this category.
Late binding is a form of dynamic programming in which exact types and target methods to invoke are not decided until runtime. Many programs bind to a precise metadata token directly in the IL. Dynamic languages, however, perform this binding very late, often times just prior to dispatching a method call.
