Sample Video Frame

Created by Zed A. Shaw Updated 2024-12-10 18:57:40
 

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 void *thing.
  • 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 switch-statements.

Type Modifiers

  • 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.

Type Qualifiers

  • 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.

Type Conversion

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
  • double
  • float
  • int (but only char and short int);
  • long

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 long into char unless you know what you're doing.

Type Sizes

The 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 INT16_MAX.
  • 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)_MAX definition 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 stdint.h:

  • 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*_t but 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_t
  • INTPTR_MIN: Minimum value of a intptr_t
  • UINTPTR_MAX: Unsigned maximum value of a uintptr_t
  • 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_t
  • PTRDIFF_MAX: Maximum value of ptrdiff_t
  • SIZE_MAX: Maximum of a size_t
Previous Lesson Next Lesson

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.