But at an application level, C pointers are a burden and a danger. They're burdensome because the programmer has to attend to details that a compiler can readily handle. For example, in C, to use a function (procedure) parameter as an output parameter (i.e., one that changes a value in the calling function), you have to pass the address of the variable that is to receive the value. This mechanism requires special attention when calling a function to code an argument as arg when it's passed to a function that defines the corresponding parameter as the same type as arg, but as &arg when the argument is passed to a function that defines the corresponding parameter as a pointer. In the called function, normal parameters are referenced as arg, whereas the value of parameters declared as pointers must be referenced as *arg. In all of these cases, a simple miscoding that incorrectly omits or adds a * or & can be fatal during program execution. By contrast, in languages like Pascal and Ada, you simply specify whether a parameter is passed by value (input only) or reference (allowing output) and all references are simple variable names, such as arg.
It's true that C++ adds references as a simpler way to implement output parameters. But C++ still retains the error-prone use of pointer parameters. And, as a good example of the damage that can be done by conventional C/C++ advice, Bjorne Stroustrup, the author of C++, goes so far as to discourage the use of references as parameters and suggests pointer parameters instead!
Pointers are often viewed as essential building blocks for dynamic data structures, such as sets and lists, and C proponents point to COBOL's (and other older languages') lack of pointers as a good reason to switch to C. But there are two ways to implement pointers: as addresses (as C does) or as "handles" (as Pascal does). The two implementations serve two distinctly different purposes. Address pointers let you directly manipulate a pointer variable to create a new pointer value (i.e., a new address). This ability is essential in many systems-level programs where access of specific memory locations (or even registers) is required. The downside of address pointers is that there's no guarantee that a computed pointer value will be the intended - or even a valid - address. As a result, a common experience in C programming is to have a program write over memory that contains the wrong data - the program's own instructions, or even the operating system's code - all due to an incorrect pointer value.
Handle pointers contain system-defined values (which may even be addresses) that cannot be directly manipulated by arithmetic operations, and which the system can check for validity before using to reference storage. Thus, handle pointers provide support for dynamic data structures, but protect the programmer from the dangers of machine-level address manipulations. A similar argument applies when comparing C's approach to storage allocation (e.g., with the malloc() function) in explicit bytes versus other languages' built-in new and delete operations to allocate memory based on variable declarations, leaving the storage size allocations to the compiler.
This discussion of pointers introduces a theme that is repeated throughout the tutorial - C was designed and is well-suited as a replacement for assembly language. But most software developers today agree that assembly language - even a great version of assembly language - isn't the right tool for most non-systems programming. Programmers who don't understand that programming with C pointers (and many other C features) is very close to assembly language programming are in trouble from the beginning. Unfortunately, most C programmers don't seem to get it.