Hardware can interpret bits in memory in various ways: as instructions, addresses, integers, floating point, fixed point, or perhaps even as character data.
But bits are bits; types are conceptually imposed over the bits, and not realized in hardware.
A type system consists of
a mechanism to define types and associate these types with language constructs
and a set of rules for type equivalence
Type checking is an attempt to ensure that a program enforces its type compability rules. Violations are genearlly called a type clash.
A language can be called strongly typed if it prohibits the application of any operation to any object that is not intended to support that application.
A language can be called statically typed if it is strongly typed and type checking can be performed at compile time. (The term is also sometimes applied to languages that can do most of their type checking at compile time with only a small portion done at run time.)
"Dynamic type checking is a form of late binding" [page 291], and is a common attribute of languages that do other forms of late binding.
See also Wikipedia's take on this
Fundamental definition: "Polymorphism allows a single body of code to work with objects of multiple types." [page 291]
This idea becomes more subtle than one might think, and often what polymorphism actually entails is part and parcel of the language definition.
For instance, Haskell's take on its polymorphism.
Denotational: a type is a set of values. This way of thinking lets us use the well-honed tools of set theory.
Constructive: a type is either built-in, or can be constructed from other types.
Abstraction-based: a type is an interface consisting of a set of operations with well-defined and consistent semantics (and that is a pretty abstract definition since it seems to cover more than just types)
Numeric types: unfortunately, a lot of languages are quite lackadaisical about just how many bytes are in their numeric types. Some languages have signed and unsigned types; some have fixed point numbers or BCD; some languages support complex numbers, rationals, imaginary numbers, and even p-adic numbers.
Scripting languages usually support multiprecision numbers.
type weekday = (sun, mon, tue, wed, thue, fri, sat);
for today := mon to fri do begin ...
var daily_attendance : array [weekday] of integer;
enum weekday {sun, mon, tue, wed, thu, fri, sat};
int main()
{
enum weekday day1 = sun;
enum weekday day2 = mon;
enum weekday day3;
day3 = day1 + day2;
}
Many languages allow constructions like 12..24 or sun..fri.
Haskell, because of its lazy evaluation scheme, even allows just 12..
type Atype = 0..20;
type Btype = 10..30;
var a: Atype;
var b: Btype;
Fundamentally, records and structures allow 1) aggregation into a single data structures components of different types and 2) a method of addressing each component of an aggregate
Usually this means 1) simply putting components adjacent to each other in memory, and 2) maintaing an offset for each component
with complex.complicated.deep.stuff do
begin
fieldx := 'STUFF GOES HERE';
fieldy := 'STUFF GOES THERE';
fieldz := 'STUFF GOES EVERYWHERE'
end;
A very common composite data structure. Most languages have significant syntactical and semantic support for arrays. Fundmentally, an array takes an index to a value.
Your text takes the approach of treating associative arrays as part of this spectrum of various types of indices; this works well with the then conflation of arrays with functions.
It is popular for programming languages to allow enumerations and ranges to used in an array specfication.
Multi-dimensional array declarations are also common in programming langauges.
While many languages treat strings largely as just an array of characters, some give strings a separate type that allows operations that are not applicable to other arrays.
Generally, most languages give strings a reasonably prominent place, both in syntax and semantics, since string processing is so fundamental to real-world problems.
Pascal was the first language to explicitly have a set type; it overloaded the "+", "*", and "-" operators to support support set union, intersection, and difference.
Generally done as a bit vector for smallish sets, and either the language forbids largish sets, or uses a more sparse approach.
Reclaiming unneeded storage: some languages, like C, leave this task to the programmer; others automate the process with garbage collection.
Failing to reclaim storage creates memory leaks; conversely, freeing storage that is still in use creates dangling references. Freeing already freed storage creates double frees.
"It would never have been a problem in the first place, however, if C had been designed for automatic bounds checks."
Yes, and I would note that it also would not have been as serious a problem had return addresses been kept on a separate stack from activation records; or if there had been hardware support for array boundaries; or a host other techniques.
Although it's past its utility, the original idea of allowing the programmer to do the pointer arithmetic behind flat array implementation was quite efficient, though modern compilers can usually better a programmer's efforts.
Try this program: test-sizeof.c
Mark-and-sweep, classic three steps:
Implementation issues galore, though, and language implementations planning to use mark-and-sweep should plan on this from the beginning
Stop-and-copy, classic unpredictable time problems (also known as "stop the world" pause problem)
Usually fold compaction into this method
As the text points out, consider equality of s == t expressed as
While the last is clearly problematic (what if s and t contain non-semantically significant bits that are not the same?) the others can be useful measures
And what about assignment? If s := t and t is a large recursive structure, the language might just do a shallow copy of t as a pointer, or maybe it does a deep copy, walking the entire structure of t and creating a fresh copy in s.
Or maybe the language supports both shallow and deep mechanisms.
A type system consists of any built-in types, mechanisms to create new types, and rules for type equivalence, type compatibility, and type inference
A strongly typed language never allows an operation to be applied to an object that does not support it. (However, the notions of strong and weak typing are not universally agreed upon.)
A statically typed language enforces strong typing at compilation time.