Sample Video Frame
Exercise 21: Advanced Data Types and Flow Control
This exercise will be a complete compendium of the available C data types and flow control structures you can use. It will work as a reference to complete your knowledge, and won't have any code for you to enter. I'll have you memorize some of the information by creating flash cards so you can get the important concepts solid in your mind.
For this exercise to be useful, you should spend at least a week hammering in the content and filling out all of the elements that are missing here. You'll be writing out what each one means, and then writing a program to confirm what you've researched.
Available Data Types
int: Stores a regular integer, defaulting to 32 bits in size.
double: Holds a large floating point number.
float: Holds a smaller floating point number.
char: Holds a single 1 byte character.
void: Indicates "no type" and is used to say a function returns nothing, or a pointer has no type as in
enum: Enumerated types, which work as and convert to integers, but give you symbolic names for sets. Some compilers will warn you when you don't cover all elements of an enum in
unsigned: Changes the type so that it doesn't have negative numbers, giving you a larger upper bound but nothing lower than 0.
signed: Gives you negative and positive numbers, but halves your upper bound in exchange for the same lower bound negative.
long: Uses a larger storage for the type so that it can hold bigger numbers, usually doubling the current size.
short: Uses smaller storage for the type so it stores less, but takes half the space.
const: Indicates that the variable won't change after being initialized.
volatile: Indicates that all bets are off, and the compiler should leave this alone and try not to do any fancy optimizations to it. You usually only need this if you're doing really weird stuff to your variables.
register: Forces the compiler to keep this variable in a register, and the compiler can just ignore you. These days compilers are better at figuring out where to put variables, so only use this if you can actually measure an improvement in speed.
C uses a sort of stepped type promotion mechanism, where it looks at two operands on either side of an expression, and promotes the smaller side to match the larger side before doing the operation. If one side of an expression is on this list, then the other side is converted to that type before the operation is done. It goes in this order:
- long double
- int (but only
If you find yourself trying to figure out how your conversions are working in an expression, then don't leave it to the compiler. Use explicit casting operations to make it exactly what you want. For example, if you have:
long + char - int * double
Rather than trying to figure out if it will be converted to double correctly, just use casts:
(double)long - (double)char - (double)int * double
Putting the type you want in parenthesis before the variable name is how you force it into the type you really need. The important thing though is always promote up, not down. Don't cast
char unless you know what you're doing.
stdint.h defines both a set of
typdefs for exact sized integer types, as well as a set of macros for the sizes of all the types. This is easier to work with than the older
limits.h since it is consistent. Here are the types defined:
int8_t: 8-bit signed integer
uint8_t: 8-bit unsigned integer
int16_t: 16-bit signed integer
uint16_t: 16-bit unsigned integer
int32_t: 32-bit signed integer
uint32_t: 32-bit unsigned integer
int64_t: 64-bit signed integer
uint64_t: 64-bit unsigned integer
The pattern here is in the form (u)int(BITS)_t where a u is put in front to indicate "unsigned," and then BITS is a number for the number of bits. This pattern is then repeated for macros that return the maximum values of these types:
INT(N)_MAX: Maximum positive number of the signed integer of bits (N), such as
INT(N)_MIN: Minimum negative number of signed integer of bits (N).
UINT(N)_MAX: Maximum positive number of unsigned integer of bits (N). Since it's unsigned, the minimum is 0 and can't have a negative value.
WARNING: Pay attention! Don't go looking for a literal
INT(N)_MAXdefinition in any header file. I'm using the
(N)as a placeholder for any number of bits your platform currently supports. This
(N)could be any number, 8, 16, 32, 64, maybe even 128. I use this notation in this exercise so that I don't have to literally write out every possible combination.
There are also macros in
stdint.h for sizes of the
size_t type, integers large enough to hold pointers, and other handy size defining macros. Compilers have to at least have these, and then they can allow other larger types.
Here is a full list should be in
int_least(N)_t: Holds at least (N) bits
uint_least(N)_t: Holds at least (N) bits unsigned
INT_LEAST(N)_MAX: Maximum value of the matching least (N) type
INT_LEAST(N)_MIN: Minimum value of the matching least (N) type
UINT_LEAST(N)_MAX: Unsigned maximum of the matching (N) type
int_fast(N)_t: Similar to
int_least*N*_tbut asking for the "fastest" with at least that precision
uint_fast(N)_t: Unsigned fastest least integer
INT_FAST(N)_MAX: Maximum value of the matching fastest (N) type
INT_FAST(N)_MIN: Minimum value of the matching fastest (N) type
UINT_FAST(N)_MAX: Unsigned maximum value of the matching fastest (N) type
intptr_t: Signed integer large enough to hold a pointer
uintptr_t: Unsigned integer large enough to hold a pointer
INTPTR_MAX: Maximum value of a
INTPTR_MIN: Minimum value of a
UINTPTR_MAX: Unsigned maximum value of a
intmax_t: Biggest number possible on that system
uintmax_t: Biggest unsigned number possible
INTMAX_MAX: Largest value for the biggest signed number
INTMAX_MIN: Smallest value for the biggest signed number
UINTMAX_MAX: Largest value for the biggest unsigned number
PTRDIFF_MIN: Minimum value of
PTRDIFF_MAX: Maximum value of
SIZE_MAX: Maximum of a
Register for Learn C the Hard Way
Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.