C Basics: Types, Operators and Control Flow

Write your first C programs and understand exactly what they do. Compile and run code with gcc or clang and fix what the compiler reports, store data in integer, character and floating-point variables, and print and read values with printf and scanf. Build expressions with C's arithmetic, comparison, logical and bitwise operators, predicting conversions and precedence, then steer a program with if, switch, while, do-while and for. Finish by learning why integers overflow, what happens when they do, and how to write and test code that prevents it.

BeginnerLesson 1 of 8 in Programming in C Course

What you will learn

  1. Turn C source code into a running program, know what the compiler does at each stage, and use its messages to find and fix errors.
    • Write, compile and run a first C program
      Read the parts of a minimal C program, compile it with gcc or clang, and run the executable from a terminal.
    • Read compiler messages and fix errors
      Tell compile-time errors, warnings, link errors, run-time errors and logic errors apart, and use compiler messages and test runs to fix them.
  2. Declare and initialize variables, choose between C's integer and floating-point types, and predict the range and precision each type gives.
    • Declare, initialize and name variables
      Create variables with a type and a meaningful name, give them starting values, and use constants for fixed values.
    • Choose an integer type and know its range
      Pick among C's signed and unsigned integer types, character and Boolean values, using their sizes, ranges and literal forms.
    • Work with floating-point numbers and their limits
      Store real numbers in float and double, predict their precision and rounding errors, and convert safely between floating-point and integer values.
  3. Format numbers and text precisely with printf, and read and check values typed by the user with scanf.
    • Format output with printf
      Use conversion specifications, field widths, precision, flags and escape sequences to produce exactly the output you intend.
    • Read and check input with scanf
      Read numbers and characters typed by the user, check that the reading succeeded, and recover from invalid input.
  4. Use C's arithmetic, assignment, comparison, logical and bitwise operators, and predict the value and type of any expression from conversions, precedence and associativity.
    • Calculate with arithmetic and assignment operators
      Apply +, -, *, / and %, integer division, compound assignment and increment and decrement, and avoid their typical traps.
    • Predict type conversions in mixed expressions
      Explain how C converts operands of different types, use casts deliberately, and avoid the signed–unsigned trap.
    • Combine conditions with relational, logical and bitwise operators
      Write correct comparisons and compound conditions, use short-circuit evaluation, and manipulate individual bits.
    • Apply precedence, associativity and evaluation order
      Predict how C groups the operators in an expression, evaluate expressions step by step, and use parentheses where grouping is unclear.
  5. Choose between alternatives with if, else and switch, and repeat work with while, do-while and for loops that start, run and stop exactly as intended.
    • Make decisions with if and else
      Write if, else if and else statements with correct conditions and braces, and avoid the common traps of C's syntax.
    • Select among cases with switch
      Use switch with case labels, break and default to choose among fixed integer values, and use fall-through only on purpose.
    • Repeat while a condition holds with while and do-while
      Write condition-controlled loops that start correctly, make progress and stop, including input loops and loops that must run at least once.
    • Count with for loops, nest loops and stop early
      Write for loops that run exactly the intended number of times, combine loops inside loops, and use break and continue deliberately.
  6. Explain what happens when a result does not fit its integer type, and write and test code that avoids or detects overflow.
    • Explain what happens when a value does not fit
      Describe unsigned wraparound, undefined signed overflow and out-of-range conversions, and connect them to real failures.
    • Write code that avoids or detects overflow
      Choose types with enough range, check operations before they overflow, and use compiler tools and boundary tests to catch overflow early.

C Types, Operators and Control Flow study guide

6 chapters

1

Build and run C programs

C code becomes a running program through a build. Learn the parts of a complete first program, compile it with a chosen language standard, and run the resulting executable. When a build or test goes wrong, use the stage of failure and the first compiler message to locate the cause. Following the edit, build, and test cycle carefully keeps an old executable from hiding a failed change and makes each repair easier to check.

1.1 From source code to a running program

A C program starts as plain text and runs as machine code. The compiler connects those two forms. Saving an edit changes the source; it does not change the executable you built earlier.

Read a complete program

Save this as hello.c:

#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}

#include <stdio.h> makes declarations for standard input and output available, including printf. Execution starts in main. In int main(void), int names the return type and void means there are no parameters. Braces enclose the body. The call to printf writes the quoted text; \n moves to a new line. return 0; ends the program and reports success to the operating system. A nonzero status can report failure. Since C99, reaching the end of main also returns zero.

Simple statements end with semicolons. The include line does not. C distinguishes uppercase and lowercase: Printf and printf are different names. Spaces between tokens usually do not affect the program, but consistent indentation makes the block structure visible.

Compile, then run

In the terminal, open the folder holding the source and enter:

gcc -std=c17 -Wall -Wextra hello.c -o hello
./hello

The first command builds an executable named hello; the second runs it from the current folder. Its output is Hello, world! followed by a newline. You can use clang in place of gcc. Without -o hello, the usual Linux or macOS output name is a.out. Windows executables use .exe. CS50's make hello is a shortcut that asks the build tool to invoke the compiler.

After changing the greeting, save, compile again, and run again. If compilation fails, an older executable may still be present. Running it shows the old behavior, so check that the build succeeded before judging your edit.

What the build does

The preprocessor handles directives such as #include, inserting header text. The compiler proper translates C into assembly language. The assembler produces object code, which can still contain unresolved names. The linker combines object code and resolves references such as printf. Library code can be copied into an executable by static linking or supplied through a shared library loaded when the program runs.

You can stop the build at intermediate stages:

OptionResult
-EPreprocessed text
-SAssembly language
-cAn object file, such as hello.o

An object file is not yet the finished executable. An executable can run without the compiler, but it is built for a particular operating system and processor. To move the program to another platform, compile its source there.

Comments and language versions

// begins a comment that ends at the newline. /* ... */ can cover several lines, but these comments cannot nest: the first */ ends the comment. Use comments to explain a decision that the code alone leaves unclear.

Select the language version explicitly. C17 supports the examples above, including // comments, long long, and declarations in for headers. C23 adds features such as bool as a keyword; it was published as ISO/IEC 9899:2024. -Wall -Wextra enables useful warnings. Compiler defaults vary, so an explicit -std=c17 keeps the intended language rules clear.

Key Takeaways

  • C source must be compiled again after each change.
  • Execution starts in main; returning zero reports success.
  • Build stages are preprocessing, compilation, assembly, and linking.
  • Use -std=c17 -Wall -Wextra to select the standard and enable warnings.

1.2 Finding the first cause of an error

The time at which a problem appears helps you find its cause. A program may fail to build, stop while running, or finish with the wrong answer. Those situations call for different checks.

Separate the kinds of problems
KindWhat you seeWhere to look
Compile-time errorNo new executableSyntax, declarations, and types
WarningA likely problem; building usually continuesThe expression named in the message
Link errorObject code exists, but the executable cannot be completedUnresolved function names or required libraries
Run-time errorA problem appears during executionOperations and values reached by that input
Logic errorThe program runs but gives an unintended answerConditions, formulas, and updates

A warning can identify invalid code or undefined behavior as well as an ordinary mistake. Successful compilation does not guarantee a correct program.

Read a diagnostic

A compiler message normally gives a source location, a severity, and a description. A message such as hello.c:5:16: error: expected ';' after expression points to a missing semicolon near line 5. Sometimes the compiler notices the problem on the next line. Inspect the reported line and the one just before it.

Fix the first error first, then compile again. An unclosed quote can make later lines look like part of a string; an unmatched brace can make later declarations appear in the wrong block. Several messages may disappear after one repair. Do not start by changing every line mentioned in the output.

For this fragment:

int total = 6
printf("%d\n", total);

The declaration needs a semicolon after 6. The printf line may be where the compiler first detects that the previous declaration did not end. Also check exact spelling, balanced parentheses and braces, and the header that declares a library function. A call to printf needs <stdio.h>; undeclared function calls are not allowed in C99 and later.

Take warnings seriously

Build with -Wall -Wextra. These options help catch an assignment in a condition, unused variables, signed–unsigned comparisons, and mismatched output formats. For example, printf("%d\n", 3.5); asks for an integer but passes a double; the format does not convert the argument for you. Fix the format to %f or deliberately convert the value. -Werror makes warnings stop the build.

Understand undefined behavior

Undefined behavior means C places no requirements on the program's behavior. Signed integer overflow, integer division by zero, and some reads of uninitialized variables can cause it. A program might stop, print an unexpected value, or appear to work. Compiler settings can change what happens.

A message such as "Floating point exception" can even result from integer division by zero on some systems. Diagnose the operation and input, not just the wording of an operating-system message. Check a divisor before executing the division.

Trace a wrong result

For a logic error, predict a small case by hand and compare it with the actual output. Add temporary printf calls to show a running total or the value tested by a condition. Change one cause at a time and rebuild after each change. Test ordinary values and boundaries such as zero, one, a negative input, and the largest expected input. A trace shows where the program first diverges from your prediction.

Key Takeaways

  • Fix the first diagnostic and rebuild before chasing later messages.
  • A warning can identify undefined behavior; compilation alone does not establish correctness.
  • Match format conversions to argument types.
  • For a wrong result, trace a small input and compare intermediate values.
  • Test boundaries as well as ordinary inputs.
2

Store data in variables of the basic types

A type determines how a stored value is interpreted and what values it can hold. Begin with declarations, starting values, and the names available inside a block. Then choose integer types by range and floating types by precision as well as range. Character codes and Boolean values fit into this picture too. Reading literals and checking limits prepares you to predict what a calculation can represent before you write it.

2.1 Variables, names, and fixed values

A variable gives a stored value a name. Its type tells C how to interpret that storage. You declare the type before using the variable, and it remains that type throughout its lifetime.

Give each variable a starting value
int count = 0;
double length_m = 1.8;
count = count + 1;

The first two lines declare and initialize variables. The third assigns a new value to an existing variable: it reads the current count, adds one, and stores the result back. Assignment is an action, so count = count + 1 is meaningful even though it would not be a mathematical equation.

Initialization happens in a declaration; assignment can happen later. In int a, b = 5;, only b is initialized. A local a has an indeterminate value until you store one. Reading it can produce unpredictable behavior, including undefined behavior. Initialize counters and totals when you declare them, such as int total = 0;.

Assignment copies the value at that moment:

int width = 4;
int area = width * 3;
width = 7;
printf("%d %d\n", width, area);

This prints 7 12. The assignment to area calculated 12 once. It did not create a formula that recalculates whenever width changes. To update area, assign a new calculation to it.

Choose readable names

An identifier can contain letters, digits, and underscores, but cannot start with a digit or be a keyword. height_cm and second_place work; 2nd_place does not, and my-score is read as subtraction. Names are case-sensitive, so total and Total differ. Names that include a meaning or unit make formulas easier to inspect.

Follow block scope

A local name is available from its declaration to the end of its block. Since C99, declarations can appear among other statements.

int count = 2;
{
    int count = 9;
    printf("%d\n", count);
}
printf("%d\n", count);

The inner declaration hides the outer name while that block runs. The output is 9 and then 2, on separate lines. These are two variables, not two assignments to one variable. Declaring the same name twice in one block is an error. Reusing names in nested blocks is allowed but makes code harder to follow, so prefer names that keep each role clear.

Name fixed values
const int SECONDS_PER_MINUTE = 60;
#define MAX_HEIGHT 8

const creates a typed variable that cannot be assigned a new value through that name. A later statement SECONDS_PER_MINUTE = 90; requires a diagnostic. The initializer gives the fixed value at the declaration.

#define creates a preprocessor replacement: later occurrences of MAX_HEIGHT are replaced by the token 8 before compilation. There is no = and no semicolon in this directive. A semicolon would become part of the replacement and could break an expression such as height > MAX_HEIGHT.

A name such as SECONDS_PER_MINUTE tells a reader why 60 appears in a calculation and gives you one place to maintain it. Capital letters are a naming convention, not a rule that makes a value constant. The declaration or directive supplies that behavior.

Key Takeaways

  • Initialize local variables before reading them.
  • Assignment copies a value once; dependent values do not update themselves.
  • A block limits where a local name can be used.
  • Names are case-sensitive and cannot begin with a digit.
  • const supplies a typed read-only variable; #define supplies a token replacement.

2.2 Choosing integer, character, and Boolean types

Integer types store whole numbers exactly within a fixed range. Choose a type by the values the program must handle, including intermediate results, and by whether negative values make sense.

Sizes and ranges

The standard guarantees minimum widths: at least 8 bits for char, 16 for short and int, 32 for long, and 64 for long long. Each type in this sequence is at least as wide as the previous one. Actual sizes depend on the platform.

TypeTypical bytes on 64-bit Linux/macOS64-bit Windows difference
char1None
short2None
int4None
long84 bytes
long long8None

sizeof(int) reports the size in bytes as a size_t; print it with %zu. The header <limits.h> supplies names such as INT_MIN, INT_MAX, UINT_MAX, LLONG_MAX, and CHAR_BIT, the number of bits per byte.

With N value bits, an unsigned integer has values 0 through 2ᴺ − 1. A two's-complement signed integer with N bits ranges from −2ᴺ⁻¹ through 2ᴺ⁻¹ − 1. Thus a 32-bit int ranges from −2,147,483,648 to 2,147,483,647, while a 32-bit unsigned int reaches 4,294,967,295. C23 requires two's complement; C17 also allowed other representations.

In an 8-bit two's-complement pattern, the highest bit has weight −128. 11111111 represents −1, and 10000000 represents −128. To form −5, start with 00000101, invert the bits to 11111010, and add one to get 11111011.

Characters are integers too

A char stores a character code in one byte. Use single quotes for a character constant, as in 'A'. In ASCII, 'A' has code 65 and '0' has code 48. printf("%c %d\n", 'A', 'A'); prints A 65 in ASCII. A character constant in C has type int, so sizeof('A') is sizeof(int).

Plain char can be signed or unsigned depending on the implementation. Use signed char or unsigned char when the numerical range matters. <stdint.h> offers exact-width types such as int32_t and uint8_t on platforms that support those exact widths; mainstream platforms do.

Read integer literals

A decimal constant such as 42 has type int when it fits. Suffixes request other types: 42u is unsigned, 42L is long, 42LL is long long, and 42ULL is unsigned long long. Hexadecimal uses 0x, as in 0x2A. A leading zero means octal: 052 also means 42, and 010 means 8. 09 is invalid octal. C23 adds binary notation such as 0b101010.

Truth values and type choice

In C17, include <stdbool.h> for bool, true, and false; C23 makes them keywords. A conversion to bool gives false for zero and true for every nonzero value, including 0.5. Print a Boolean with %d to see 0 or 1.

Use int for ordinary counts and loop counters. Use long long or a suitable exact-width type when the range can exceed about 2.1 billion. Unsigned types suit bit patterns, but subtraction below zero wraps to a large value, so they still need range checks.

Key Takeaways

  • Use the type limits, not a guessed size for long.
  • A 32-bit signed int reaches 2,147,483,647.
  • Unsigned values have no negative range and wrap on underflow.
  • A leading zero starts an octal integer literal.
  • Plain char has implementation-dependent signedness.
  • In C17, Boolean names come from <stdbool.h>.

2.3 Decimal values and limited precision

A floating-point type stores an approximation to a real number. It has a large range, but only a fixed number of significant digits. Range and precision answer different questions: how large a value can be, and how finely nearby values can be distinguished.

Choose a floating type

On common IEEE 754 systems, float occupies 4 bytes and carries about 6–7 significant decimal digits. double occupies 8 bytes and carries about 15–16. Their largest finite values are about 3.4 × 10³⁸ and 1.8 × 10³⁰⁸. long double is at least as precise as double, with a platform-dependent format. double is a useful default for measured quantities.

Constants such as 2.0, 3.14, and 1e-3 are double; 1e-3 means 0.001. Add f for float, as in 2.5f, or L for long double. The constant 2 is an integer, so it can lead to integer division where 2.0 would produce floating-point division.

See the stored approximation

Many decimal fractions have no exact finite binary form. On a common double-precision system:

printf("%.20f\n", 0.1);
printf("%.17f\n", 0.1 + 0.2);

The outputs are 0.10000000000000000555 and 0.30000000000000004. The extra digits expose the stored approximation. They explain why 0.1 + 0.2 == 0.3 can be false.

Use a tolerance when comparing calculated measurements:

#include <math.h>
/* inside main */
if (fabs(a - b) < 1e-9) {
    printf("Close enough\n");
}

fabs returns an absolute difference. Choose the tolerance for the scale and precision of the values; 1e-9 is an example, not a universal setting. Some Linux builds need -lm at the end of the compiler command to link math functions.

Precision affects large whole numbers

A binary double represents every integer through 2⁵³ exactly. A binary float stops representing every integer above 2²⁴ = 16,777,216: assigning 16,777,217 to such a float gives 16,777,216. More range does not mean every whole-number step remains available.

Printing rounds the stored binary value. printf("%.2f\n", 2.675); commonly prints 2.67, because the stored value is slightly less than 2.675. For exact amounts in whole cents, use an integer such as long long cents = 1999;, then format the display at the end.

Convert and round deliberately

Conversion to an integer discards the fraction toward zero: (int)3.99 is 3, and (int)-3.99 is −3. round from <math.h> returns a floating value rounded to the nearest whole number, with halves away from zero: round(-2.5) gives −3.0. Convert that result to an integer only when it fits. A floating-to-integer conversion whose whole part is out of range has undefined behavior.

Under IEEE arithmetic, results outside the finite range can become infinity. Nonzero floating division by zero can produce infinity; zero divided by zero produces NaN, meaning "not a number." A NaN compares unequal even to itself. These are IEEE floating-point behaviors; integer division by zero has undefined behavior.

Key Takeaways

  • Floating types have limited precision as well as limited range.
  • Compare calculated measurements with a suitable tolerance.
  • Printing rounds the stored approximation.
  • Floating-to-integer conversion truncates toward zero and requires a representable result.
3

Display output and read input with printf and scanf

A program needs a clear exchange of text and values. Format output so a reader can distinguish labels, fields, and rounded measurements, then match each input conversion to the variable receiving it. Reading text has several possible outcomes: all requested values, only some values, or no conversion at all. Checking those outcomes and the unread text makes later calculations depend on values the program has actually obtained.

3.1 Printing values with the intended format

printf turns values into text using a format string. Ordinary characters print as written. A conversion beginning with % tells it how to display the next argument.

Match each conversion to its argument
printf("%d samples, %.2f kg\n", 4, 1.5);

This prints 4 samples, 1.50 kg and a newline. The conversions correspond to arguments in order. A format does not convert the type of the supplied argument to whatever type you intended.

Value type or displayConversion
int%d or %i
unsigned int%u
long, long long%ld, %lld
size_t%zu
double, or a promoted float%f, %e, %g
long double%Lf
Character, string literal%c, %s
Unsigned hexadecimal, octal%x or %X, %o

A float argument is promoted to double in printf, so both use %f; %lf is equivalent here. Passing 3 to %f is a type mismatch, not a request to print 3.0. Use 3.0 or (double)3. A mismatched type or a missing argument can cause undefined behavior. Build with warnings enabled so literal format strings are checked.

Set width and precision

A width is a minimum number of characters, including a sign and decimal point. A floating precision after the dot controls decimal places for %f. In %8.2f, eight is the minimum width and two is the number of places after the decimal point. A wider number is printed in full; width does not cut it off.

With the integer 42, %5d gives 42, %-5d gives 42 , %05d gives 00042, and %+d gives +42. Without a precision, %f prints six decimal places.

printf("%-6s%8s\n", "Item", "Price");
printf("%-6s%8.2f\n", "Tea", 2.5);
printf("%-6s%8.2f\n", "Cake", 12.75);

Each label occupies a left-aligned field of six, followed by a right-aligned price field of eight. Both prices have two decimal places, so their decimal points line up. Spaces come from the field widths, not from automatic separation between arguments.

Scientific and general displays

%e uses scientific notation: 12345.678 prints as 1.234568e+04 at the default precision. For %g, precision counts significant digits, with six by default. It uses scientific notation when the decimal exponent is below −4 or at least the precision, and removes trailing zeros. Thus %g prints 0.0001 as 0.0001, 0.00001 as 1e-05, and 1000000.0 as 1e+06. It does not simply choose the shortest possible spelling.

Print special characters

Inside a C string, \n means newline, \t means tab, \\ means a backslash, and \" means a quote. A percent sign in a format is %%: printf("100%%\n"); prints 100% and a newline.

printf adds no spaces or line endings on its own. printf("Count:"); printf("%d", 4); prints Count:4. A terminal may delay text without a newline; fflush(stdout); sends a pending prompt before input is requested.

Key Takeaways

  • Match conversions to actual argument types.
  • Field width is a minimum; %f precision counts decimal places.
  • %g precision counts significant digits.
  • Write %% for a literal percent sign in a format.
  • printf adds no spacing or newline unless the format supplies it.

3.2 Reading input and checking what was read

scanf reads text, converts it, and stores values in variables. Check its result before using those variables. Text that begins with digits can be only partly accepted, so a successful conversion and a fully valid input line are different things.

Supply a destination of the right type
int count;
double mass_kg;
if (scanf("%d %lf", &count, &mass_kg) != 2) {
    return 1;
}

The & passes each variable's address so scanf can store into it. %d needs an int, %u an unsigned integer, %ld a long, and %lld a long long. For floating input, %f needs a float and %lf a double. Unlike printf, these two conversions are not interchangeable. A missing & or wrong destination type can cause undefined behavior.

%d reads decimal. %i also recognizes hexadecimal and leading-zero octal input; with %i, the text "010" means 8. Use %d for ordinary decimal counts.

Check the return value

scanf returns the number of assignments completed. For two requested values, success means 2. A failed conversion leaves its destination unchanged. It may still be uninitialized, so do not use it unless the return count confirms success. EOF means input ended or an input error occurred before the first conversion completed.

For scanf("%d:%d", &h, &m), the colon must appear literally. Input "14:30" fills both variables. Input "14 30" fills h, then fails to match the colon and leaves m unchanged. The return value is 1.

Understand whitespace and partial input

Numeric conversions skip leading spaces, tabs, and newlines. %c reads the very next character, including a leftover newline. Use scanf(" %c", &choice) to skip whitespace before reading a visible character. Avoid a trailing newline in the format: it can make the call wait for a later non-whitespace character.

With %d, "12abc" stores 12 and leaves "abc" unread. "3.7" stores 3 and leaves ".7". "abc" stores nothing and returns 0. Calling the same conversion again without consuming bad input can fail forever on the same character.

Recover from a failed conversion
int n, result;
while ((result = scanf("%d", &n)) != 1) {
    if (result == EOF) {
        return 1;
    }
    int ch;
    while ((ch = getchar()) != '\n' && ch != EOF) {
    }
    printf("Try again: ");
    fflush(stdout);
}

getchar returns an int, allowing it to represent both a character and EOF. The inner loop discards the failed line. The outer loop retries, but still accepts a numeric prefix such as 12 in "12abc". Requiring the next character to be '\n' checks the stricter rule of one integer immediately followed by a newline; that rule also rejects trailing spaces. A policy that permits spaces or a final line ending at EOF needs to handle those explicitly.

After a successful conversion, check the allowed range, such as 1 through 8 for a height setting. scanf does not safely reject an out-of-range numeric value: a value too large for its destination has undefined behavior. Use test inputs that fit the chosen type. To calculate a result such as mass divided by height squared, validate positive measurements and keep the square grouped as height_m * height_m.

Key Takeaways

  • Pass variable addresses and use %lf to read a double.
  • Require the return count to match the number of values needed.
  • A successful conversion can leave extra input unread.
  • Consume bad input before retrying a failed numeric conversion.
  • Check allowed values after reading; input conversion has its own type range limits.
  • Use " %c" when a character read should skip whitespace.
4

Compute with operators and expressions

An expression combines values, types, and operators. Trace integer division and updates first, then follow the conversions that occur at each operation. Comparisons and logical guards control whether a calculation is reached, while bitwise operations act on individual bits. Finally, distinguish the grouping set by precedence from the sequencing needed when an expression changes a variable. Both the value and the type of an intermediate result matter.

4.1 Arithmetic, assignment, and one-step updates

Every C expression has a value and a type. The type determines how an operation works. Integer division, for example, cannot preserve a fractional result even if you later store it in a floating-point variable.

Quotients and remainders

+, -, and * add, subtract, and multiply. / divides, and % gives an integer remainder. Multiplication, division, and remainder bind more tightly than addition and subtraction.

When both division operands are integers, C truncates the quotient toward zero. 17 / 5 is 3, -7 / 2 is −3, and 7 / -3 is −2. The remainder satisfies the division identity: multiplying the quotient by the divisor and adding the remainder gives the dividend. Thus -7 % 2 is −1 and 7 % -3 is 1. A nonzero remainder has the dividend's sign.

To check evenness, use n % 2 == 0. To check oddness for positive or negative integers, use n % 2 != 0; checking for exactly 1 misses negative odd values. % needs integer operands; <math.h> provides fmod for floating remainders. An integer divisor of zero makes both / and % undefined, so check it before the operation.

For 135 minutes, 135 / 60 gives two whole hours and 135 % 60 gives 15 remaining minutes. For a positive decimal integer, % 10 extracts its last digit and / 10 removes that digit: 4096 gives 6 and 409.

Keep fractions when you need them
int correct = 45, total = 60;
double percent = 100.0 * correct / total;

The floating constant makes the multiplication floating-point, and the division gives 75.0. In correct / total * 100, the first operation is integer division and gives zero. Multiplying that zero cannot recover the lost fraction. Integer multiplication first, correct * 100 / total, gives 75 here, provided the product fits.

Store a new value

An assignment evaluates its right side and stores the converted value on the left. The assignment expression has that stored value, so a = b = 0; sets both variables to zero, grouping from the right.

Compound assignments combine a calculation and storage: x += 5 adds five to x, and x /= 2 divides x by two using its current type. The whole right operand belongs to the calculation. If x is 4, x *= 2 + 1 makes it 12, because it multiplies by three.

Prefix and postfix updates

++ adds one and -- subtracts one. On a line by themselves, i++; and ++i; have the same final effect. Inside a larger expression, postfix yields the old value; prefix yields the new value.

StartStatementFinal values
a = 5y = a++;y = 5, a = 6
a = 5y = ++a;y = 6, a = 6

Keep modifications easy to order. i = i++ + 1; and printf("%d %d\n", n, n++); have unsequenced conflicting uses of the same variable and undefined behavior. Separate the read, the update, and the output into statements when their order matters.

Key Takeaways

  • Integer division truncates toward zero.
  • A nonzero integer remainder has the dividend’s sign.
  • Use a floating operand before division if you need a fraction.
  • Postfix yields the old value; prefix yields the updated value.
  • Keep conflicting reads and updates in separate statements.

4.2 Types are decided before a result is stored

C converts values as each operator is applied. The destination variable receives the finished result; it does not reach back into the expression and change earlier operations.

Convert before dividing
int sum = 7, count = 2;
double a = sum / count;
double b = (double)sum / count;
double c = (double)(sum / count);

a becomes 3.0: the two integer operands produce integer 3, which is then converted. b becomes 3.5: the cast converts sum before division, so count is converted to double too. c becomes 3.0: its cast is outside the division and arrives after the fraction has been discarded.

The same rule applies within a longer expression. 7 / 2 * 2.0 gives 6.0, because the first result is integer 3. 7 / 2.0 * 2 gives 7.0. Work through the operators in their grouping order, marking the type of each intermediate value.

Find a common arithmetic type

If either operand is floating-point, arithmetic generally converts to the widest floating type involved: long double, then double, then float. For integers, small types first undergo integer promotion. On platforms with 8-bit char, 16-bit short, and 32-bit int, every value of the smaller types fits in int, so those types promote to int.

With unsigned char a = 200, b = 100;, a + b is the integer 300 on an 8-bit-byte, 32-bit-int system. It does not wrap at 255 during addition. Storing 300 into an 8-bit unsigned character reduces it to 44. Promotion can also produce signed overflow: two unsigned short values of 50,000 multiply as int, but 2,500,000,000 exceeds a 32-bit INT_MAX.

For signed and unsigned types, rank and representable range govern the common type. int with unsigned int converts to unsigned. A higher-rank signed type can keep the operation signed when it represents every value of the unsigned type; otherwise unsigned arithmetic can still result.

The signed–unsigned trap

With a 32-bit unsigned integer, -1 < 0u is false. The signed −1 converts to the unsigned value 4,294,967,295 before comparison. Likewise, 2u - 10 gives 4,294,967,288. A sizeof result is unsigned too, which matters when comparing it with a possibly negative count. Enable warnings and keep related quantities in compatible types.

Storage conversions can lose information

A floating-to-integer conversion truncates toward zero. Its whole part must fit, or behavior is undefined. An integer converted to an N-bit unsigned type is reduced modulo 2ᴺ; −1 becomes the maximum unsigned value. Under C17, an out-of-range integer conversion to a signed type has an implementation-defined result or raises an implementation-defined signal. That conversion is distinct from undefined signed arithmetic overflow.

Character constants also take part in arithmetic. '7' - '0' gives 7 because C guarantees consecutive codes for decimal digits. In ASCII, letters are consecutive too, but do not assume that property for every character set. For integer formulas, multiplying before dividing can preserve useful information only if that product fits its calculation type.

Key Takeaways

  • The destination type does not change earlier arithmetic.
  • Cast an operand before the operation that needs a wider or floating type.
  • Small integers promote before arithmetic.
  • Mixed signed and unsigned operands can change comparisons and subtraction.
  • Out-of-range conversion and signed arithmetic overflow follow different rules.
  • Track intermediate types as well as final values.

4.3 Conditions and bit patterns

A condition asks whether something is true. A bitwise operation works on the individual bits of an integer. C gives these jobs different operators, even when their symbols look similar.

Compare values and combine tests

The comparisons <, >, <=, >=, ==, and != produce an int: 1 for true and 0 for false. In a condition, zero is false and any nonzero value is true. Thus if (count) tests whether count is nonzero. == compares two values; = assigns a new one.

&& means both conditions must be true, || means at least one must be true, and ! reverses truth. They return 0 or 1. A range from 1 through 8 needs two comparisons:

height >= 1 && height <= 8

1 <= height <= 8 does not express that range in C. It groups as (1 <= height) <= 8, so the second comparison receives 0 or 1 and is always true. To test outside the range, write height < 1 || height > 8.

Negating a compound condition changes both parts: !(a && b) is equivalent to !a || !b, and !(a || b) to !a && !b. These are De Morgan's laws. They explain why being outside an interval needs an "or," even though being inside needs an "and."

Put a guard before the risky operation
if (d != 0 && n / d > 2) {
    printf("Above two\n");
}

&& evaluates its left side first. If that side is false, it skips the right side. || skips its right side when the left side is true. This short-circuit evaluation lets a guard protect a later operation. Reversing the two parts above would divide before checking the divisor. For signed integers, a separate range issue exists when INT_MIN is divided by −1 on a two's-complement system; guard that pair as well when arbitrary values are possible.

A leap-year test combines divisibility and exceptions: (y % 4 == 0 && y % 100 != 0) || y % 400 == 0. The year 2000 passes the 400 test; 1900 fails because the century exception applies.

Work one bit at a time

Bitwise & keeps bits set in both operands; | keeps bits set in either; ^ keeps bits that differ; ~ inverts bits. With 12 (1100) and 10 (1010), the results are 8, 14, and 6 for &, |, and ^.

Shifts move bits. For nonnegative values where the result fits, x << k multiplies by 2ᵏ, and x >> k discards k low bits, dividing by 2ᵏ with the remainder discarded. Use unsigned types for bit work. Under C17, shifting by a negative count or at least the promoted type's width is undefined; left-shifting a negative signed value is undefined, and right-shifting one is implementation-defined.

Bit positions start at zero. flags | (1u << 3) sets bit 3; flags & ~(1u << 3) clears it; (flags >> 3) & 1u reads it. 1 & 2 is 0 because no bit is shared, while 1 && 2 is 1 because both values are nonzero. Bitwise operators evaluate both operands and cannot replace logical guards.

Key Takeaways

  • Comparisons and logical operators produce 0 or 1.
  • A range test joins two comparisons with &&.
  • Short-circuit guards must come before the protected operation.
  • Use logical operators for truth and unsigned bitwise operators for masks.
  • Bit positions start at zero; shift counts must be within the promoted type’s width.

4.4 Grouping expressions and ordering effects

Two questions help you read an expression: which operands belong to each operator, and when do operations with side effects happen? Precedence and associativity answer the grouping question. They do not generally fix the order of operand evaluation.

Follow precedence

Operators higher in this table bind more tightly. Each row is lower than the preceding row; the operators separated by "then" have distinct levels.

From high to lowOperators
Postfixx++, x--, calls
Unary++x, --x, +, -, !, ~, casts, sizeof, address &
Multiplicative*, /, %
Additive+, -
Shift<<, >>
Relational<, <=, >, >=
Equality==, !=
Bitwise&, then ^, then `
Logical&&, then `
Conditional?:
Assignment=, +=, and other compound assignments
Comma,

Parentheses override grouping. 2 + 3 * 4 is 14; (2 + 3) * 4 is 20.

Use associativity at equal levels

Most binary arithmetic operators group left to right. 10 - 4 - 3 means (10 - 4) - 3, which is 3. 100 / 10 / 2 gives 5. Assignment groups right to left, so a = b = 0 stores zero in both variables. Grouping describes the expression structure; it does not establish a general left-to-right execution rule.

For int a = 3, b = 4, c = 5;, trace a + b * c > 20 && c % 2 == 1. Multiplication gives 20, addition gives 23, and 23 > 20 gives 1. Remainder gives 1, and its equality test gives 1. The final 1 && 1 is 1.

Protect bit tests and assignments with parentheses

x & 1 == 0 means x & (1 == 0), so it always produces zero. Write (x & 1) == 0 to test the low bit. Addition also binds more tightly than shifting: 1 << 2 + 1 is 1 << 3, or 8.

Assignment binds below comparison. a = b == c stores the comparison's 0 or 1 in a. In while ((ch = getchar()) != '\n'), the inner parentheses make the assignment receive the character first. Without them, ch would receive a comparison result.

Choose one value or sequence two actions

condition ? first : second evaluates the condition and then only the selected value expression. max = (a > b) ? a : b; chooses a value; an if can choose a block of actions. The comma operator evaluates its left expression first and yields its right expression's value. It can coordinate two updates in a for header. Commas between function arguments are separators and do not establish that order.

Keep side effects separate

C leaves many operand and argument orders unspecified. n++ * n++ has unsequenced changes to the same variable, so it has undefined behavior. Extra parentheses do not fix the missing sequencing. Use separate statements when one action must precede another. &&, ||, ?:, and the comma operator provide specific sequencing rules; ordinary arithmetic precedence supplies no such guarantee.

Key Takeaways

  • Precedence determines grouping; it does not generally determine evaluation order.
  • At one arithmetic level, grouping is usually left to right.
  • Parenthesize a bitwise result before comparing it.
  • The conditional operator evaluates only the selected value expression.
  • The comma operator sequences operands; argument-separating commas do not.
  • Separate conflicting updates to avoid undefined behavior.
5

Control program flow with conditions and loops

Control flow determines which statements run and how often. Use conditions to select a branch, exact integer cases to select a switch entry, and loops to repeat a task until its stopping rule is met. Traces make the order visible: starting values, tests, body statements, and updates. Pay particular attention to boundary inputs and to the destination of break or continue inside nested constructs, where a small syntax change can alter the whole path.

5.1 Choosing the right branch

An if statement chooses whether to run an action. Its condition must be inside parentheses. Zero means false; every nonzero value means true.

Use braces to show the branch
if (temperature < 0) {
    printf("Freezing\n");
} else {
    printf("At or above freezing\n");
}

The first block runs for a negative temperature. At exactly zero, the comparison is false and the else block runs. Read the boundary directly from <, <=, >, or >=; the message should agree with that boundary.

Without braces, only the next statement is controlled:

if (temperature > 30)
    printf("Hot\n");
printf("Drink water\n");

The second message runs at every temperature. Indentation helps people, but does not change the grammar. Braces let you add a second statement to a branch without accidentally leaving it outside.

Order an else-if chain

An if–else if–else chain runs the first branch whose condition is true, then skips the rest. For score bands, begin with the highest lower bound:

if (score >= 90) {
    grade = 'A';
} else if (score >= 80) {
    grade = 'B';
} else if (score >= 70) {
    grade = 'C';
} else {
    grade = 'F';
}

A score of 85 fails the first test and passes the second. A score of 90 passes the first. If you tested score >= 70 first, 85 would enter that branch and never reach the intended B branch. Separate if statements behave differently: each condition is tested independently, so several blocks may run.

Watch two small syntax mistakes

if (x = 5) assigns 5, then treats that nonzero value as true. Use if (x == 5) to compare. With if (x = 0), assignment still happens but the condition is false. Warnings help reveal accidental assignments in conditions.

In if (x > 0);, the semicolon is an empty controlled statement. A block placed after it runs independently, even when x is negative. Do not add a semicolon between an if condition and its intended block.

Pair each else explicitly

An else attaches to the nearest earlier if that does not already have an else. In nested decisions, use braces to make the intended pairing clear. Nesting is useful when failed tests need different messages: first test whether a measurement is valid, then test whether its value is in the desired band. Combining both with && works when only one shared action is needed.

Put exceptions before broad rules

Leap years provide a useful ordered decision. First test divisibility by 400, which means leap year. Next test divisibility by 100, which means not a leap year. Then test divisibility by 4; otherwise the year is not a leap year. For 2000, the first branch applies. For 1900, the second applies. Checking only divisibility by four would miss the century exception. The order matters because broad rules can include cases with a more specific exception.

Key Takeaways

  • An else-if chain runs only its first true branch.
  • Braces control which statements belong to a branch.
  • == compares; = assigns.
  • A semicolon after an if condition creates an empty branch.

5.2 Selecting fixed cases with switch

A switch chooses a starting point using one integer value. It is useful for a command character or menu number with several fixed alternatives. Execution can then continue into later cases unless you stop it.

Match a case and leave it
switch (choice) {
    case 1:
        printf("Start\n");
        break;
    case 2:
        printf("Pause\n");
        break;
    default:
        printf("Unknown choice\n");
        break;
}

With choice equal to 2, the program starts at case 2, prints Pause, and leaves the switch at break. If no case matches, execution starts at default. Without a default, an unmatched value skips the switch.

The controlling expression must have an integer type, including char or an enumeration. Each case must be a distinct integer constant expression, such as 3, 'q', or a suitable #define name. In C17, a const int variable is not an integer constant expression for a case label. Floating values and strings cannot be switch controlling values.

Understand fall-through

Case labels mark entry points, not separate blocks that automatically end. Remove the first break above and a choice of 1 prints both Start and Pause. The program does not test whether choice is 2 before continuing through that case. Execution stops only at a control transfer or the end of the switch.

Several labels can share one action:

case 'y':
case 'Y':
    printf("Yes\n");
    break;

Both characters enter the same action. When one case deliberately does work and continues into another, explain the intention with a comment. C23 also offers [[fallthrough]];. Warnings can help find a missing break, but grouping labels deliberately needs no repeated action.

Give case-local variables a block

Under C17, a declaration cannot directly follow a case label. Use braces around that case's statements:

case 3: {
    int delay = 5;
    printf("%d\n", delay);
    break;
}

The block also keeps delay local to that case. C23 relaxes the label-before-declaration restriction, but braces still make the scope clear.

Choose between switch and if

Use switch for exact integer alternatives. Use if–else if for inequalities, conditions involving several variables, or floating measurements. Equal-width score bands can sometimes be converted to exact cases by integer division. With scores from 0 through 100, score / 10 gives 8 for 85; cases 9 and 10 can share the top band. Validate that domain so negative or excessive scores are not silently assigned a band.

A switch inside a loop

A break leaves the nearest enclosing switch or loop. Inside a switch that is itself inside a loop, the break ends only the switch. To end the menu loop, set a flag checked by its condition, or return from main when the program should end. A continue in that switch applies to the enclosing loop, sending it to its next iteration. Trace which construct each transfer targets before deciding whether the menu stops.

Key Takeaways

  • A switch selects an entry label using one integer value.
  • Execution falls through until break, another control transfer, or the end.
  • Case values must be distinct integer constant expressions.
  • Use a block for a case-local declaration in C17.
  • A switch’s break does not also end an enclosing loop.

5.3 Repeating until the data says stop

A while loop tests a condition before each pass. A do–while loop tests after the body, so it runs at least once. Choose the form that puts the test where the program needs it.

Trace a condition-controlled calculation
int n = 4096, sum = 0;
while (n > 0) {
    sum += n % 10;
    n /= 10;
}

n % 10 extracts a digit, and n /= 10 removes it. The values after the passes are:

Passsumn
16409
21540
3154
4190

The next condition is false, so the loop ends with a digit sum of 19. If n begins at zero, the body runs zero times and the sum remains zero. Negative inputs also skip this loop, so a program that accepts them needs a defined policy.

A counting loop sets a counter, tests it, and updates it. Other loops make progress by reading data or reaching break. For repeated integer halving of 100, the values are 50, 25, 12, 6, 3, and 1: six passes are needed to reach one.

Let input control repetition
int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
    sum += x;
    count++;
}
if (count != 0) {
    printf("%.2f\n", (double)sum / count);
}

For input "5 8 3 0", the sentinel zero ends the loop and is not added. The sum is 16, the count is 3, and the displayed average is 5.33. The return-count test protects use of x; the count test protects the division if the first value is zero. This calculation also requires inputs and totals that fit in int.

Without the sentinel check, while (scanf("%d", &x) == 1) stops on failed numeric conversion or end of input. At a terminal, Ctrl+D normally signals end of input on Linux and macOS; on Windows use Ctrl+Z followed by Enter.

Run once before testing
int height;
do {
    printf("Height (1-8): ");
    fflush(stdout);
    if (scanf("%d", &height) != 1) {
        return 1;
    }
} while (height < 1 || height > 8);

The prompt and read happen before the range test. Values zero and nine repeat; values one and eight are accepted. The semicolon after while (...) is required in this form. The conversion check ends this example on nonnumeric input; the range test alone cannot handle failed conversion.

Make progress on every continuing path

break exits the innermost loop immediately. continue skips the rest of the body and returns to the condition in a while loop. If an update lies below a continue, it is skipped too. A loop can then keep testing the same value forever.

Before running a loop, identify what changes and why the stop condition will eventually be reached. A deliberate while (1) can stop through break; a missing counter update is a different situation. Ctrl+C stops a program stuck in a terminal. Test the smallest input and the value at the stopping boundary to catch zero-pass and one-extra-pass mistakes.

Key Takeaways

  • while tests first; do–while tests after at least one pass.
  • A sentinel stops input without becoming part of the data.
  • Initialize accumulators outside the loop.
  • A continuing path must make progress toward the stop condition.
  • A while continue can skip a later update.
  • Check for zero data items before calculating an average.

5.4 Counting passes and combining loops

A for loop keeps a counting pattern in one header. Nested loops repeat an inner task for each outer pass. Both become easier to check when you list the actual counter values and identify every exit.

Follow the header in order
for (int i = 0; i < 3; i++) {
    printf("%d ", i);
}

Initialization runs once. Then the condition is tested, the body runs if it is true, and the update runs before the next test. The output is 0 1 2 . The variable declared in the header is local to the loop; declare it before the loop if you need its final value afterward.

For nonnegative n, i = 0; i < n; i++ runs n times, visiting zero through n − 1. With i <= n, it runs one extra time. For a positive step s from a through an included b, the count is (b - a) / s + 1 using integer division, provided the arithmetic and counter updates fit. From 2 through 20 in steps of 3, the values are 2, 5, 8, 11, 14, 17, 20: seven passes.

Keep the accumulator outside
int total = 0;
for (int i = 1; i <= 100; i++) {
    total += i;
}

The final total is 5050. The initialization must happen once before the loop. Moving int total = 0; into the body creates a fresh total on every pass, losing the earlier work.

Choose counters that can reach the end

A semicolon directly after the for header makes the body empty. A following block then runs once after the loop. Use braces attached directly to the header to make the body visible.

An unsigned countdown with i >= 0 cannot stop that way: unsigned values are always nonnegative, and decrementing zero wraps to the maximum. A floating counter that repeatedly adds 0.1 can skip the exact value 1.0, so x != 1.0 is unreliable as a stopping rule. Count with an integer and calculate a measurement such as x = i / 10.0 inside the body.

Build rows with nested loops

For each outer pass, the inner loop starts and runs its own sequence. Three outer passes and four fixed inner passes execute the inner body twelve times. When the inner bound depends on the row, count each row separately.

int height = 3;
for (int row = 1; row <= height; row++) {
    for (int s = 0; s < height - row; s++) printf(" ");
    for (int h = 0; h < row; h++) printf("#");
    printf("\n");
}

The rows contain two spaces and one hash, one space and two hashes, then three hashes. The newline belongs after the inner loops, so each outer pass makes one row.

Skip work or stop early

continue in a for loop goes to the update and then the next test. A loop adding only odd values from 1 through 9 can skip even values and finish with 25. break leaves the innermost loop immediately. In nested loops, an inner break does not stop the outer loop.

A for loop is often the clearest choice for a known counter range; while suits data-controlled repetition, and do–while suits work that must happen before testing. Rewriting a for loop as a while loop requires special care with continue, because the update must still happen on that path.

Key Takeaways

  • A for loop runs initialization once and update after each continuing pass.
  • Check the visited values to catch off-by-one errors.
  • Use integer counters with enough range.
  • Nested loops restart the inner sequence for each outer pass.
  • An inner break leaves only the inner loop.
  • A for continue still runs the header update.
  • Keep a running total’s initialization outside the repeated body.
6

Recognize and prevent integer overflow

Finite integer ranges affect calculations even when the final answer looks small enough. Distinguish unsigned wraparound, signed overflow, and values lost during conversion, then inspect the types of intermediate operations. Prevention starts before a risky calculation: widen an operand, prove bounds, or use a checked operation. Counter ranges and boundary tests help you find failures that ordinary small inputs and short test runs can miss.

6.1 What happens when an integer does not fit

An integer's range is finite. A mathematical answer can be valid and still be too large for the C type used to calculate it. The consequences depend on whether the operation is unsigned, signed, or a conversion.

Unsigned arithmetic wraps

An N-bit unsigned result is reduced modulo 2ᴺ. With a 32-bit unsigned int, UINT_MAX + 1u gives zero, 0u - 1u gives 4,294,967,295, and 3u - 5u gives 4,294,967,294. The result is defined, but it can be wrong for the task: subtracting five items from a stock of three should not produce billions of items.

For uint8_t b = 255;, b = b + 1; stores zero. On a typical 32-bit-int system the addition first occurs as int and produces 256; converting that value back to an 8-bit unsigned type produces zero. Small unsigned storage does not mean every intermediate operation has that small type.

Signed arithmetic overflow is undefined

With a 32-bit int, adding one to INT_MAX needs 2,147,483,648. C does not define the signed arithmetic result. Hardware may appear to wrap to INT_MIN, but the compiler can optimize based on signed overflow never occurring. A check such as if (x + 1 < x) can be removed for signed integers, because every defined signed addition of one gives a larger value.

Do not calculate first and then inspect the damage. An undefined operation has already happened before a later test runs.

A wider destination can be too late
int a = 50000, b = 50000;
long long product = a * b;

The multiplication is performed as int, so it overflows on a 32-bit-int system. The destination's width cannot repair it. (long long)a * b changes the operation's type before multiplication and produces 2,500,000,000.

Factorials grow quickly: 12! is 479,001,600 and fits in a 32-bit signed integer; 13! is 6,227,020,800 and does not. With 32-bit unsigned arithmetic, 13! wraps to 1,932,053,504. A 64-bit signed long long holds 20! = 2,432,902,008,176,640,000, but not 21!.

Conversion has separate rules

Converting an integer to an unsigned type uses modulo reduction. Under C17, converting an out-of-range integer to a signed type gives an implementation-defined result or signal. Converting a floating value whose truncated whole part does not fit in the integer destination has undefined behavior. Reading an out-of-range number through a numeric scanf conversion is also undefined.

Range failures can have large effects

Ariane 5's first flight on June 4, 1996 was lost after a floating value could not fit a 16-bit signed integer in its inertial reference software. That program was written in Ada; the lesson about checking conversion ranges applies to C too.

A signed 32-bit seconds counter starting at the Unix epoch reaches 2,147,483,647 at 03:14:07 UTC on January 19, 2038. Systems using that time representation need a wider one. In 2015, the FAA also addressed Boeing 787 generator-control software that could overflow after 248 days of continuous power. These failures show why both long running time and intermediate values belong in a range estimate.

Floating-point limits are different: common IEEE systems can produce infinity, and a float can lose whole-number precision before reaching its maximum. Unsigned integer wrap produces an ordinary numeric value; signed overflow offers no reliable result at all.

Key Takeaways

  • Unsigned arithmetic uses modulo reduction.
  • Signed arithmetic overflow has undefined behavior.
  • A wider destination cannot fix a narrow intermediate operation.
  • Integer conversion, floating conversion, and arithmetic overflow have different rules.
  • Estimate values over the full operating duration.

6.2 Preventing overflow before it happens

Start with the largest value each calculation can produce. Include intermediate products and counter updates, not just the final answer. Then choose a type or a pre-check that keeps every operation in range.

Widen the calculation early

A 32-bit signed integer holds about 2.1 billion, while a 64-bit signed long long holds up to 9,223,372,036,854,775,807. A millisecond counter reaches the 32-bit signed limit in about 24.9 days. Use a 64-bit type when a duration can run longer.

Write (long long)a * b to widen an integer product before it is evaluated. Constants matter too: 86400LL * days starts the multiplication in long long. (long long)(a * b) converts only after the narrower product, so it cannot prevent that product's overflow. Wider types still have limits; check the largest possible values against them.

Check addition using subtraction

For int a and int b, include <limits.h> and test:

if ((b > 0 && a > INT_MAX - b) ||
    (b < 0 && a < INT_MIN - b)) {
    printf("Sum out of range\n");
} else {
    int sum = a + b;
    printf("%d\n", sum);
}

Each subtraction is protected by the sign of b, keeping the check itself representable. For 2,000,000,000 plus 200,000,000, INT_MAX - b is 1,947,483,647, below a, so the addition is refused. The equality case fits: when a equals INT_MAX - b, the sum is exactly INT_MAX.

Check positive multiplication by division

For positive int operands, a > INT_MAX / b means the product would exceed the maximum. The condition avoids calculating the product and avoids a zero divisor because b is positive. In a factorial calculation, the test before multiplying 12! by 13 detects that 479,001,600 is greater than 165,191,049. Stop before that multiplication.

For unsigned stock, test stock >= requested before subtracting. Switching to unsigned alone does not prevent a nonsensical wrapped result.

Rewrite risky intermediate expressions

The midpoint (low + high) / 2 can overflow even when its answer fits. When 0 <= low <= high and both are int, use low + (high - low) / 2. The difference is nonnegative and within range, and the result never exceeds high. These conditions matter; the formula is not a general proof for arbitrary signed endpoints.

Loop counters need range too. A 16-bit short cannot reach 40000, so a loop using it to count that far cannot terminate as intended. Use a counter type that can represent the bound and any final update.

Use checked arithmetic and tests

C23 provides <stdckdint.h> with ckd_add, ckd_sub, and ckd_mul. For example, ckd_add(&r, a, b) stores a result and returns true when the mathematical answer does not fit r. GCC and clang also provide __builtin_add_overflow, __builtin_sub_overflow, and __builtin_mul_overflow as compiler extensions.

Warnings catch some constant overflow and signed–unsigned mistakes. In GCC or clang, -fsanitize=undefined -g can report signed overflow reached during a test; -ftrapv requests traps for signed addition, subtraction, and multiplication overflow. Exercise zero, one, negative one, the largest expected input, and values at and just below limits. A test tool checks the paths you run, so keep the range reasoning in the code.

Key Takeaways

  • Widen an operand before a risky operation.
  • Use sign-aware pre-checks for signed addition.
  • The division pre-check for multiplication requires positive operands.
  • Unsigned subtraction still needs a range check.
  • The safer midpoint formula requires ordered nonnegative endpoints.
  • Checked arithmetic reports whether the result fits its destination.
  • Boundary tests and sanitizers complement range reasoning.

C Types, Operators and Control Flow examples

6 Walkthroughs

1

Build a greeting and diagnose a failed edit

9 steps
Follow a first program from source to execution, then use diagnostics and a trace to repair problems.
  1. 1
    IntroductionFollow the build

    A saved C program is text. Running it requires an executable produced from that text. Keep the two files’ roles clear: an edit changes the source, while a successful build replaces the executable.

    You will build a short greeting, inspect what each stage does, and diagnose an edit that fails. The central habit is to check the build result before using a run as evidence about the new source.

  2. 2
    ExplanationRead the program’s frame

    The program begins at main:

    #include <stdio.h>
    int main(void)
    {
        printf("Ready\n");
        return 0;
    }
    

    The header supplies the declaration of printf. The body prints a line, then returns success. void means main has no parameters here; int names the type of its return value. Braces hold the body, and simple statements end with semicolons.

  3. 3
    ExampleBuild and run it

    Save the source as ready.c. In its folder, enter:

    gcc -std=c17 -Wall -Wextra ready.c -o ready
    ./ready
    

    The build selects C17 and useful warnings, then names the executable ready. The run prints Ready followed by a newline. On Linux or macOS, ./ selects the current folder. You can use clang in the same command position; Windows executables use an .exe suffix.

  4. 4
    ConnectionSee where object code fits

    Preprocessing handles the include directive. Compilation then produces assembly, and assembly produces object code. Linking resolves references and completes the executable; library code may be supplied by a shared library at run time.

    The options -E, -S, and -c stop at progressively later stages. In particular, -c stops at an object file: it has not yet completed the linking step needed for the executable.

  5. 5
    ChallengePredict the next run

    You build the greeting, then save an edit changing "Ready" to "Begin". You run the existing executable without compiling. What text appears, and how do you change it?

    ---reveal---

    It still prints Ready. The executable reflects the earlier successful build. Compile the saved edit and run the new executable to print Begin. Saving source alone does not update machine code.

  6. 6
    ExampleRepair the earliest error

    An edit leaves this line without a semicolon:

    printf("Begin\n")
    return 0;
    

    The compiler may point at the first line or at return, where it notices the missing end of the expression statement. Add the semicolon after the call and rebuild. Start with this earliest cause even if later messages mention other lines; a single syntax error can disrupt the compiler’s reading of the following code.

  7. 7
    TipTreat warnings as useful evidence

    A warning usually allows a build to continue, but it can identify a serious mistake. printf("%d\n", 2.5); passes a double where the format requires int, which has undefined behavior.

    Repair the type mismatch instead of accepting whatever a test run prints. Warnings from -Wall -Wextra help expose such mistakes; -Werror makes them stop the build. For a wrong calculation, print intermediate values and compare them with a small hand trace.

  8. 8
    ChallengeSeparate a failed build from a wrong run

    After a successful greeting build, a new edit fails to compile. Running ./ready still prints a greeting. Does that show the edit is valid, and what should you inspect next?

    ---reveal---

    No. The old executable can remain after a failed build. Inspect the first compiler error, including the preceding source line, fix it, and rebuild successfully. Then run that new executable and compare its output with your prediction. The existing greeting says nothing about the failed edit.

  9. 9
    RecapUse one clear repair cycle

    Keep each test connected to the source that actually built. Save the edit, compile, read the first diagnostic, repair its cause, and build again before running.

    If the program builds but calculates the wrong value, use a small trace and boundary inputs. If an operation has undefined behavior, change the code so it cannot occur; a normal-looking output is not a dependable result for that operation.

2

Choose storage for counts and measurements

13 steps
Trace assignment and scope, then connect integer range and floating precision to practical type choices.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionAsk what a value needs
  2. 2
    ExampleA copied value stays copied
  3. 3
    ExplanationKeep names inside their blocks
  4. 4
    ChallengeFollow a hidden name
  5. 5
    ExplanationCompare ranges before choosing
  6. 6
    ExampleRead a bit pattern and a literal
  7. 7
    ChallengeChoose a portable range
  8. 8
    ExplanationSee why decimal fractions drift
  9. 9
    ExampleCompare an allowed error
  10. 10
    ChallengeSeparate conversion from rounding
  11. 11
    Key InsightPrecision can fail before range
  12. 12
    ChallengeMatch the type to the task
  13. 13
    RecapCheck storage in two ways
3

Read measurements and print clear results

11 steps
Match formats to types, inspect partial input, and follow a checked measurement calculation.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionConnect typed values with text
  2. 2
    ExplanationRead the output format
  3. 3
    ExampleBuild an aligned display
  4. 4
    ChallengePredict a padded integer
  5. 5
    ExplanationRead into the right destinations
  6. 6
    ExampleNotice a partial conversion
  7. 7
    TipDo not read a leftover newline by accident
  8. 8
    ChallengeTrack what a failed read preserves
  9. 9
    ExampleCheck the calculation’s domain
  10. 10
    ChallengeCombine the read and the display
  11. 11
    RecapUse two checks at input
4

Read an expression from types to effects

17 steps
Work through division, conversions, logical guards, masks, grouping, and updates in a defined order.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionMark both values and types
  2. 2
    ExampleSplit a quantity into whole parts
  3. 3
    ExplanationDistinguish the two increments
  4. 4
    ChallengeTrace a compound update
  5. 5
    ExamplePut the floating operand early enough
  6. 6
    Key InsightSmall unsigned values may calculate as signed int
  7. 7
    ChallengeFollow a signed value into unsigned arithmetic
  8. 8
    ExplanationJoin complete comparisons
  9. 9
    ExampleMake a guard run first
  10. 10
    ExampleChange one flag without disturbing others
  11. 11
    ChallengeCompare truth with shared bits
  12. 12
    ExplanationGroup before tracing
  13. 13
    TipPut the mask inside the comparison
  14. 14
    ConnectionChoose a value without evaluating both
  15. 15
    ChallengeCan parentheses repair two updates?
  16. 16
    ChallengeCombine grouping with a range test
  17. 17
    RecapUse three passes through an expression
5

Trace decisions and build a running total

18 steps
Work through branches, a switch menu, sentinel input, and nested counting with deliberate exits.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionFollow the path actually taken
  2. 2
    ExplanationUse the first true branch
  3. 3
    TipBraces and equality each matter
  4. 4
    ChallengeFind the first matching band
  5. 5
    ExplanationSelect a command entry
  6. 6
    ExampleShare an action deliberately
  7. 7
    ChallengeFind the target of break
  8. 8
    ExplanationRead before using input
  9. 9
    ExampleTrace the sentinel calculation
  10. 10
    ChallengeHandle an immediate stop
  11. 11
    ConnectionAsk once with do-while
  12. 12
    ExplanationCount exactly the intended visits
  13. 13
    ExampleKeep a total between passes
  14. 14
    TipAvoid counters that miss the exit
  15. 15
    ExampleCount rows separately
  16. 16
    ChallengeTrace a nested early exit
  17. 17
    ChallengeCombine a flag and a menu loop
  18. 18
    RecapCheck every route to the next pass
6

Make a product safe before computing it

12 steps
Distinguish overflow rules, widen at the correct point, and check boundary cases without causing the failure being tested.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionInspect the arithmetic before storage
  2. 2
    ExampleFind the narrow operation
  3. 3
    ExplanationUnsigned has a defined but possibly wrong result
  4. 4
    ChallengeDoes a late cast repair the product?
  5. 5
    ExampleWiden the operation itself
  6. 6
    ExplanationTest addition without adding
  7. 7
    ExampleTest a positive product by dividing
  8. 8
    ChallengeCheck the boundary correctly
  9. 9
    Key InsightA safe answer can hide an unsafe sum
  10. 10
    TipUse tools and exercise the limits
  11. 11
    ChallengeCheck a complete range design
  12. 12
    RecapMake the proof happen before the operation

C Types, Operators and Control Flow practice questions

50 Practice Questions in 5 sets

Set 1: Build programs and follow stored values10 Practice Questions

Question 1 of 10

A program was built successfully, then its greeting was edited and saved. Running the existing executable still prints the old greeting. What action makes the edit take effect?
  1. Run the same executable again.
  2. Change the executable’s output name.
  3. Compile the saved source again.
  4. Add another newline to the greeting.
Show answerHide answer
Answer: C
The executable contains the result of the earlier build. Saving source text does not rebuild that file, so compile the edited source and then run the new executable. Running the old executable again uses the same earlier machine code and cannot pick up the changed greeting.

Question 2 of 10

A compiler reports a missing semicolon, followed by several errors on later lines. What should you repair first?
  1. The first reported syntax problem.
  2. The last reported syntax problem.
  3. Every later line before rebuilding.
  4. The executable’s run-time input.
Show answerHide answer
Answer: A
Start with the first error and inspect the line it names and the previous line. A missing semicolon can confuse the compiler’s interpretation of later statements. Repairing all later lines first can change correct code that was only reported because of the earlier mistake.

Question 3 of 10

What values are printed by this code?

int units = 6;
int total = units * 4;
units = 9;
printf("%d %d\n", units, total);
  1. 9 36
  2. 6 24
  3. 6 36
  4. 9 24
Show answerHide answer
Answer: D
The declaration of total calculates six times four once and stores 24. The later assignment changes units to nine but does not recalculate total, so the output is 9 and 24. Expecting 36 treats the earlier assignment as a live formula linking the two variables.

Question 4 of 10

A value must hold negative results and positive counts as large as 6,000,000,000 on both typical 64-bit Linux and Windows systems. What standard integer type provides the needed range?
  1. int
  2. long long
  3. unsigned int
  4. long
Show answerHide answer
Answer: B
A standard long long has at least 64 bits and can represent these positive and negative values. A typical Windows long is only 32 bits even when Windows itself is 64-bit. Unsigned int also has insufficient range on these systems and cannot represent negative values.

Question 5 of 10

What decimal value does the C17 integer literal 014 represent?
  1. 14
  2. 10
  3. 12
  4. 16
Show answerHide answer
Answer: C
The leading zero makes 014 an octal literal. Its digits represent one group of eight plus four, giving twelve. Reading it as decimal fourteen ignores the base prefix; a decimal literal for fourteen is written 14 with no leading zero.

Question 6 of 10

A calculated double differs very slightly from a target double after several decimal operations. What comparison fits a measurement that permits a small absolute error?
  1. Compare the absolute difference with the allowed error.
  2. Compare their printed values at the maximum precision.
  3. Convert both values to int before checking equality.
  4. Require exact equality after assigning both to double.
Show answerHide answer
Answer: A
An absolute-difference test such as fabs(actual - target) < tolerance checks the error the measurement permits. Exact equality can fail because decimal calculations introduce binary rounding differences. Converting to int discards fractions and can hide a difference much larger than the permitted error.

Question 7 of 10

What is printed between the vertical bars by printf("|%04d|", 23);?
  1. | 23|
  2. |2300|
  3. |23 |
  4. |0023|
Show answerHide answer
Answer: D
The width requests at least four characters and the zero flag supplies leading zeros, so 23 becomes 0023. Ordinary right alignment would use two leading spaces, but that is the behavior of %4d without the zero flag. Width does not append zeros to the value.

Question 8 of 10

Given int a = 2, b = 9;, a call to scanf("%d:%d", &a, &b) receives "17 42". What return value and stored values result?
  1. Return 2; a = 17, b = 42.
  2. Return 1; a = 17, b = 9.
  3. Return 0; a = 2, b = 9.
  4. Return 1; a = 17, b = 0.
Show answerHide answer
Answer: B
The first %d stores 17. The input then has a space where the format requires a literal colon, so matching stops and b keeps its old value, nine. The return count is one; whitespace between numbers cannot replace punctuation required by the format.

Question 9 of 10

On a system with 8-bit unsigned char and 32-bit int, what does this print?

unsigned char a = 230, b = 40;
int sum = a + b;
unsigned char small = sum;
printf("%d %u\n", sum, (unsigned)small);
  1. 14 14
  2. 270 270
  3. 14 270
  4. 270 14
Show answerHide answer
Answer: D
Both character values promote to int before addition, so sum receives 270 without wrapping at 255. Conversion into small then reduces 270 modulo 256, giving 14. Predicting 14 for both values applies the small storage range to the promoted addition, where it does not belong.

Question 10 of 10

The command gcc -std=c17 -c sensor.c succeeds. What did it produce, and what build stage is still needed before ordinary execution?
  1. An executable; preprocessing is still needed.
  2. Assembly text; assembling is still needed.
  3. An object file; linking is still needed.
  4. Preprocessed C; compilation is still needed.
Show answerHide answer
Answer: C
The -c option compiles and assembles the source into object code, normally sensor.o, then stops before linking. Linking is still needed to create the executable and resolve external references. Assembly text is the earlier output selected by -S, not by -c.
Set 2: Read input and calculate with C types10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A variable is declared double distance_m;. What scanf conversion and argument match its type?
  1. scanf("%f", &distance_m)
  2. scanf("%lf", &distance_m)
  3. scanf("%lf", distance_m)
  4. scanf("%d", &distance_m)
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

What are -23 / 6 and -23 % 6 in C17?
  1. −4 and 1
  2. −3 and 5
  3. −4 and −5
  4. −3 and −5
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

For int distance = 19, time = 4;, what expression calculates a double speed that keeps the fractional part?
  1. (double)distance / time
  2. (double)(distance / time)
  3. distance / time
  4. (int)(distance / (double)time)
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

An int read with %d accepts the input "46kg". What value is stored, and what input remains unread immediately after the conversion?
  1. 46; nothing remains.
  2. 0; "46kg" remains.
  3. 46; "kg" remains.
  4. No value; "kg" remains.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

For nonnegative int values total and count, what condition checks that the integer average is above 5 without dividing by zero?
  1. total / count > 5 && count != 0
  2. count != 0 && total / count > 5
  3. count != 0 & total / count > 5
  4. count == 0 || total / count > 5
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

What value does 2 + 5 * 3 % 7 produce?
  1. 1
  2. 0
  3. 7
  4. 3
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

What text does printf("|%6.1f|", 8.375); print with ordinary rounding to the nearest tenth?
  1. | 8.375|
  2. |8.4 |
  3. | 8.4|
  4. |0008.4|
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

What values are printed?

int n = 8;
int a = n++;
int b = ++n;
a *= b % 4;
printf("%d %d %d\n", a, b, n);
  1. 16 10 10
  2. 18 10 10
  3. 16 9 10
  4. 24 11 11
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

With 32-bit int and unsigned int, what does this print?

int delta = -3;
unsigned int limit = 2;
printf("%d %u\n", delta < limit, limit + delta);
  1. 1 4294967295
  2. 1 -1
  3. 0 1
  4. 0 4294967295
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

An unsigned value is flags = 10u. What value results from setting bit 2, counting the lowest bit as bit 0?
  1. 12
  2. 14
  3. 8
  4. 2
Show answerHide answer

Save this lesson to see this answer.

Set 3: Trace branches, switches, and loops10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What is printed?

int level = 2;
if (level = 0) {
    printf("Low");
} else {
    printf("Other");
}
  1. Low
  2. LowOther
  3. Nothing is printed.
  4. Other
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

How many passes run in for (int i = 4; i < 19; i += 4)?
  1. 3
  2. 4
  3. 5
  4. 15
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A score is 76. What character is assigned?

if (score >= 70) grade = 'C';
else if (score >= 85) grade = 'B';
else grade = 'F';
  1. C
  2. B
  3. F
  4. No character is assigned.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

What is printed when mode is 2?

switch (mode) {
case 1: printf("A"); break;
case 2: printf("B");
case 3: printf("C"); break;
default: printf("D");
}
  1. B
  2. BCD
  3. BC
  4. ABC
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

What final values do n and passes have?

int n = 45, passes = 0;
while (n > 2) {
    n /= 2;
    passes++;
}
  1. n = 1, passes = 5
  2. n = 2, passes = 5
  3. n = 3, passes = 4
  4. n = 2, passes = 4
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

What total is printed?

int total = 0;
for (int r = 1; r <= 4; r++) {
    for (int c = 1; c <= r; c++) {
        if (c == 3) break;
        total += c;
    }
}
printf("%d\n", total);
  1. 10
  2. 7
  3. 13
  4. 20
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A loop body must prompt once before it can test the first answer. What loop form directly expresses that order?
  1. while with a test before the body
  2. do–while
  3. for with a test before the body
  4. switch with a default label
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

What sum results?

int sum = 0;
for (int i = 2; i <= 8; i++) {
    if (i % 3 == 0) continue;
    sum += i;
}
  1. 35
  2. 9
  3. 26
  4. 8
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

How many times is "tick" printed?

int running = 1, passes = 0;
while (running && passes < 3) {
    switch (passes) {
    case 1: break;
    default: printf("tick\n");
    }
    passes++;
}
  1. 1
  2. 3
  3. 0
  4. 2
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

Input is "9 4 0 7". What count and average are printed?

int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
    sum += x;
    count++;
}
if (count) printf("%d %.1f\n", count, (double)sum / count);
  1. 2 6.5
  2. 3 4.3
  3. 3 6.7
  4. 4 5.0
Show answerHide answer

Save this lesson to see this answer.

Set 4: Diagnose conversions and range failures10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What value does 0u - 1u produce when unsigned int has 32 value bits?
  1. −1
  2. 2,147,483,647
  3. 4,294,967,295
  4. 0
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

With 32-bit int values a = 60000 and b = 40000, what expression safely computes their exact product in a typical 64-bit long long?
  1. (long long)(a * b)
  2. (long long)a * b
  3. a * b
  4. (double)(a * b)
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

For unsigned flags = 6u, what result does flags & 2u == 0u produce?
  1. 1
  2. 2
  3. 6
  4. 0
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

For arbitrary int values a and b, what test detects signed addition overflow before evaluating a + b?
  1. (b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)
  2. (b > 0 && a > INT_MAX - b) || (b < 0 && a > INT_MIN - b)
  3. (b > 0 && a >= INT_MAX - b) || (b < 0 && a <= INT_MIN - b)
  4. (b > 0 && a < INT_MAX - b) || (b < 0 && a < INT_MIN - b)
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

On a platform with 16-bit unsigned short and 32-bit int, two unsigned short variables each hold 48000. What happens when their product is evaluated without casts?
  1. They multiply unsigned and wrap modulo 65536.
  2. They promote to unsigned int and wrap at 2³².
  3. They promote to int, whose product overflows.
  4. They promote to long long and produce the exact product.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A C17 program adds one to an int already equal to INT_MAX. What result can portable C code rely on?
  1. The result must be INT_MIN on every system.
  2. The result must remain equal to INT_MAX.
  3. The result must be converted to unsigned int.
  4. No result is guaranteed by the language.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

Two nonnegative int endpoints satisfy low <= high. On a 32-bit-int system they are 1,800,000,000 and 2,000,000,000. What expression computes their midpoint without overflowing?
  1. (low + high) / 2
  2. low + (high - low) / 2
  3. low + high / 2
  4. low / 2 + high
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A C17 program converts the representable double value −6.875 to int. What value is stored?
  1. −6
  2. −7
  3. 6
  4. −6.875
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A positive int factorial accumulator is f, and the next multiplier is positive int i. What condition identifies that f * i would exceed INT_MAX before multiplication?
  1. f >= INT_MAX / i
  2. f / i > INT_MAX
  3. f > INT_MAX / i
  4. f * i > INT_MAX
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

An int calculation gives the expected output in one build but triggers a signed-overflow report under -fsanitize=undefined. What conclusion should guide the repair?
  1. The successful build establishes that the result is portable.
  2. The report can be fixed by using a wider printf conversion.
  3. The report guarantees the int wrapped to a specific value.
  4. The reached overflow must be prevented in the calculation.
Show answerHide answer

Save this lesson to see this answer.

Set 5: Combine input, control flow, and safe arithmetic10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A program requests two double measurements in one scanf call. What return value confirms that both assignments completed?
  1. 0
  2. 2
  3. 1
  4. EOF
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

What value is printed?

int total = 0;
for (int r = 2; r <= 5; r++) {
    for (int c = 1; c < r; c++) {
        if (c % 2 == 0) continue;
        total += r;
    }
}
printf("%d\n", total);
  1. 14
  2. 18
  3. 40
  4. 23
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A 32-bit int days is 30000. What expression correctly computes its duration in milliseconds in a 64-bit long long?
  1. 86400000LL * days
  2. (long long)(86400000 * days)
  3. 86400000 * days
  4. (long long)(86400 * days) * 1000
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

What does this C17 fragment guarantee about its output?

int n = 4;
printf("%d %d\n", n++, n++);
  1. It prints 4 and 5 because arguments run left to right.
  2. It prints 5 and 4 because arguments run right to left.
  3. It has undefined behavior from unsequenced modifications.
  4. It prints either 4 and 5 or 5 and 4 as defined alternatives.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

What is printed when a = -2 and b = 7?

if (a > 0)
    if (b > 0) printf("Both");
    else printf("A only");
  1. A only
  2. Both
  3. BothA only
  4. Nothing is printed.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

Why does this loop stop making progress after printing zero once?

int i = 0;
while (i < 4) {
    if (i == 1) continue;
    printf("%d\n", i);
    i++;
}
  1. At one, continue exits the loop before the next test.
  2. At one, continue skips the update, so i stays one.
  3. At zero, the condition prevents the first update.
  4. At four, the loop body resets i to one.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

An unsigned stock value is 7u and a requested amount is 11u. What policy prevents a wrapped stock while preserving a valid subtraction when enough stock is available?
  1. Subtract only when stock >= requested.
  2. Subtract, then reject when the new stock is negative.
  3. Subtract only when stock <= requested.
  4. Subtract, then require the new stock to be nonzero.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

With 32-bit unsigned int, what value does this code leave in flags?

unsigned int flags = 13u;
flags &= ~(1u << 2);
flags |= 1u << 1;
  1. 15
  2. 9
  3. 11
  4. 7
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A menu starts with running = 1 and actions = 0. Input is "s q s". What is actions after this loop?

char cmd;
while (running && scanf(" %c", &cmd) == 1) {
    switch (cmd) {
    case 'q': running = 0; break;
    case 's': actions++; break;
    }
}
  1. 0
  2. 1
  3. 2
  4. 3
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

What value is assigned to rate?

int completed = 14, planned = 20;
double rate = completed / planned * 100.0;
  1. 70.0
  2. 0.7
  3. 14.0
  4. 0.0
Show answerHide answer

Save this lesson to see this answer.

Stuck on your own question? Homework Help

Key Terms

91 Key Terms, each with its definition

A

Accumulator
An accumulator keeps a running result across loop passes, starting with a suitable initial value outside the repeated work.
The digit-sum accumulator begins at zero and adds one extracted digit on each pass of the loop.
Assignment
An assignment computes a value and stores it in an existing destination, converting to the destination type.
The assignment count = count + 1; reads the old value and replaces it with a value one larger.
Assignment count
The assignment count returned by scanf is the number of destinations successfully filled before the call stopped.
An assignment count of two for a three-measurement read leaves the third destination unconfirmed.
Associativity
Associativity determines how operators at the same precedence level group, without generally fixing evaluation order.
Division’s left-to-right associativity groups 24 / 4 / 2 as (24 / 4) / 2, giving three.

B

Bit mask
A bit mask is an integer pattern used to select, set, clear, or inspect particular bits in another value.
The bit mask 1u << 5 selects bit five, counting the least significant bit as position zero.
Bitwise operator
A bitwise operator acts on individual bits of integer operands to combine, invert, or shift their bit patterns.
The bitwise operator & keeps only the common set bits of twelve and ten, giving eight.
Block scope
Block scope makes a local name available from its declaration through the end of its enclosing block.
Block scope prevents using a temporary declared inside an if block after that block has ended.
Boolean value
A Boolean value represents false or true; conversion to C’s bool maps zero to false and every nonzero value to true.
After including <stdbool.h> in C17, bool ready = 2; stores the Boolean value true.
Boundary test
A boundary test runs values at or near a meaningful limit to check behavior where a rule or range changes.
A boundary test for addition includes a sum equal to INT_MAX and a pair that would exceed it by one.
Branch
A branch is one path of statements selected by a decision, such as the if part or the else part of a conditional.
A score program with an else-if chain reaches only one branch, beginning with the first true condition.
Break statement
A break statement immediately leaves the nearest enclosing switch or loop and continues after that construct.
A break statement inside a menu switch leaves that switch while the surrounding while loop continues.

C

Case label
A case label names a distinct integer constant expression that can serve as an entry point in a switch.
The case label case 4: is valid in C17; an ordinary const int variable cannot supply its value.
Cast
A cast explicitly requests conversion to a named type, using a parenthesized type before an expression.
The cast in (double)sum / count takes effect before division, preserving a fractional quotient.
Character constant
A character constant is a single-quoted character form whose value is an integer code; ordinary C character constants have type int.
The ASCII character constant 'A' can print as the letter A with %c or the number 65 with %d.
Checked arithmetic
Checked arithmetic calculates while reporting whether the exact mathematical result fits the chosen destination type.
C23’s checked arithmetic operation ckd_mul can report a product that does not fit its destination.
Comma operator
The comma operator evaluates its left operand before its right operand and yields the value of the right operand.
The comma operator orders the updates in i++, j--; commas between printf arguments do not.
Compile-time error
A compile-time error is a problem detected while translating source code that prevents a successful new build.
An unmatched closing parenthesis causes a compile-time error before a new executable can be built.
Compiler
A compiler translates a program from a source language into another form, such as assembly or machine code.
The compiler checks a C declaration and reports a missing semicolon before creating a new executable.
Compound assignment
A compound assignment combines an operator with storing its result back into the left-hand destination.
The compound assignment total += item; updates the running total without changing the item.
Condition
A condition is an expression whose zero or nonzero value decides which control-flow path is followed.
The condition in if (items) is false for zero, while a negative nonzero count takes the branch.
Conditional operator
The conditional operator uses condition ? a : b to choose one value expression after testing the condition.
With a zero condition, the conditional operator in condition ? a : b evaluates b and skips a.
Continue statement
A continue statement skips the remaining loop body and moves to the loop’s next continuation point.
A continue statement in a for loop reaches the header update; in a while loop it returns to the test.
Conversion specification
A conversion specification is a percent-led part of a format that identifies a value’s required type and presentation.
The conversion specification %lld matches a long long argument; %d cannot automatically convert it.

D

Dangling else
A dangling else is an else in nested conditionals whose attachment can be unclear to a reader without braces.
The dangling else rule attaches an else to the nearest unmatched if, regardless of indentation.
Declaration
A declaration introduces a name and its type so C can check how the named object or function is used.
The declaration int count = 0; gives the name an integer type and also supplies its starting value.
Default label
A default label supplies a switch entry point for a controlling value that matches no case label.
A default label can send an unexpected menu number to an error message instead of skipping the switch.
Do-while loop
A do-while loop executes its body before checking its repeating condition, guaranteeing at least one body execution.
A do-while loop reads the first range-checking answer in its body before deciding whether to ask again.

E

End of input
End of input means the incoming stream has no more characters available to read, distinct from ordinary nonmatching text.
A numeric input loop stops at end of input, but it can also stop earlier on nonnumeric text.
Escape sequence
An escape sequence uses a backslash in a C character or string literal to represent a special character.
The escape sequence \n adds a newline inside a C string; printf uses %% for a percent sign.
Evaluation order
Evaluation order describes when operands and side effects are computed, a separate issue from expression grouping.
Function arguments in C17 have no required left-to-right evaluation order despite their written positions.
Exact-width type
An exact-width type is an integer typedef, when available, whose name fixes its exact bit width and signedness.
The exact-width type uint8_t, when provided, holds values from zero through 255.
Executable
An executable is a built program in a form its target operating system can load and run on the intended processor.
Running ./hello starts the executable in the current folder, without invoking the C compiler again.
Exit status
An exit status is the value a program reports to its calling environment when it ends, indicating success or failure.
An exit status of zero from main lets a calling script recognize successful completion.

F

Fall-through
Fall-through is execution continuing from one switch case into the following code without an intervening control transfer.
Fall-through after a missing break can run the following case’s print without testing its label again.
Field width
A field width is the minimum number of characters reserved for a formatted output value, including its sign and decimal point.
A field width of six in %6d adds four spaces before 42, while a seven-digit value still prints in full.
Floating-point value
A floating-point value represents a real-number quantity using a fixed precision and a scale, often with rounding.
A length stored as a floating-point value may approximate the typed decimal without matching it exactly.
For loop
A for loop places initialization, a repeating condition, and an update in one header around a repeated body.
A for loop starting at zero with i < n and i++ makes n passes for nonnegative n when updates fit.
Format string
A format string combines literal text and conversion specifications that control how an input or output call handles values.
The format string in printf("Mass: %.1f kg\n", mass) supplies a label, decimal precision, and a unit.

I

Identifier
An identifier is a name used in C code, such as a variable name, with spelling rules and case-sensitive matching.
The identifier distance_m is valid; 2distance cannot be a name because it starts with a digit.
Implementation-defined behavior
Implementation-defined behavior is a choice the C implementation must document where the standard allows several behaviors.
Out-of-range integer-to-signed conversion in C17 has implementation-defined behavior or raises a signal.
Implicit conversion
An implicit conversion changes a value’s type automatically when an operation or assignment requires a different type.
Adding an int to a double applies an implicit conversion to the integer operand before addition.
Infinite loop
An infinite loop repeats without reaching an exit, often because its tested state never changes as intended.
A continue before the only update can create an infinite loop that keeps testing one unchanged value.
Initialization
Initialization gives an object its initial value as part of its declaration, before later assignments change it.
Initialization of total to zero before a loop supplies the first addition with a defined starting value.
Integer division
Integer division divides integer operands and discards the fractional part of the quotient toward zero.
Integer division in 22 / 5 gives four even when the completed result is assigned to a double.
Integer literal
An integer literal is a whole-number constant written in source with a base notation and optional type suffix.
The integer literal 0x30 uses hexadecimal, while 48u writes that value as unsigned decimal.
Integer overflow
Integer overflow occurs when the mathematical result of integer arithmetic lies outside the result type’s range.
An int product can cause integer overflow before its result reaches a long long destination.
Integer promotion
An integer promotion converts a small integer type to int, or to unsigned int if int cannot represent its full range.
On a 16-bit-short, 32-bit-int system, integer promotion lets unsigned short operands multiply as int.
Intermediate result
An intermediate result is a value computed within a larger expression before a later operation or assignment.
The sum in (low + high) / 2 is an intermediate result that can overflow even when the midpoint fits.
Iteration
An iteration is one execution of a loop’s body, after its controlling test allows that pass to begin.
A for loop visiting 3, 6, 9, and 12 has four iterations even though the final counter update reaches 15.

L

Linker
The linker combines object code and resolves references to named code and data to produce a completed program.
The linker resolves the reference to printf; its library code may be supplied by a shared library.
Logic error
A logic error makes a program calculate or choose something other than its intended result, even if it runs.
A score-band test in the wrong order creates a logic error that assigns the wrong grade.
Logical operator
A logical operator combines or reverses truth values, producing zero or one through &&, ||, or !.
The logical operator in ready && valid tests whether both stored values are nonzero.

M

Macro
A macro defines a preprocessor replacement that is substituted into later source before normal compilation.
With #define LIMIT 8, the token LIMIT is replaced by 8 wherever that macro applies.
Matching failure
A matching failure occurs when the available input cannot match the conversion or literal character expected by scanf.
Letters at the start of a %d conversion cause matching failure and remain unread for a retry.

N

NaN
A NaN is an IEEE floating-point value meaning "not a number," used for results such as zero divided by zero.
A NaN compares unequal to itself, so ordinary equality cannot treat it like an ordinary measured value.
Nested loop
A nested loop is a loop inside another loop, with the inner sequence performed again for each reached outer pass.
A nested loop that makes four inner passes for each of three rows executes its inner body twelve times.

O

Off-by-one error
An off-by-one error makes a loop or boundary calculation include one too many or one too few values.
Replacing i < n with i <= n in a zero-based counter loop causes an off-by-one error.
Operand
An operand is a value or expression to which an operator is applied; its type helps determine how the operation behaves.
In sum / count, the operands’ types determine whether division keeps a fractional result.
Out-of-range conversion
An out-of-range conversion tries to represent a source value outside the destination type’s allowed values.
An out-of-range conversion of a huge double to int is undefined when the whole part cannot fit.
Output buffering
Output buffering holds produced text temporarily before sending it onward, which can delay a prompt without a newline.
A prompt can wait in output buffering until fflush(stdout) sends it before an input call.

P

Postfix increment
A postfix increment adds one to a variable while yielding its old value for use in the surrounding expression.
With n = 3, the postfix increment in old = n++; gives old three and leaves n at four.
Precedence
Precedence determines which operators bind more tightly to their operands when parentheses do not specify grouping.
Multiplication’s higher precedence groups 4 * 2 together before addition in 3 + 4 * 2.
Precision
Precision is the amount of significant information a numeric format can retain, distinct from its largest magnitude.
Limited float precision loses single-integer steps at magnitudes still far below the type’s maximum.
Preprocessor
The preprocessor handles directives such as #include and macro replacement before the main C translation stages.
The preprocessor inserts declarations from a standard header before the compiler checks their use.

R

Range check
A range check tests whether inputs or a proposed result satisfy bounds before an unsafe operation is performed.
A stock range check compares available and requested amounts before unsigned subtraction could wrap.
Relational operator
A relational operator compares numerical values using <, <=, >, or >= and produces int zero or one.
The relational operator in height >= 1 checks one boundary; another comparison checks the other.
Remainder
A remainder is the value left by C’s integer % operation, with a nonzero result having the dividend’s sign.
The remainder -22 % 5 is −2 because the truncated quotient is −4 and the division identity must hold.
Rounding error
A rounding error is the difference introduced when a value or calculation is replaced by a nearby representable value.
Several decimal additions can produce a small rounding error that makes an exact equality test fail.

S

Sanitizer
A sanitizer is a run-time checking tool added during compilation that reports certain invalid operations on executed paths.
An undefined-behavior sanitizer can identify the multiplication where a signed counter calculation overflows.
Sentinel
A sentinel is a designated value that signals the end of data and is recognized before ordinary processing.
A zero sentinel can end an average calculation without increasing either its sum or its data count.
Shift
A shift moves the bit pattern of an integer left or right by a count subject to the promoted type’s width and sign rules.
Shifting an unsigned value right by two positions discards the two lowest bits of its representation.
Short-circuit evaluation
Short-circuit evaluation skips a logical operator’s right operand when its left operand already decides the result.
Short-circuit evaluation in count != 0 && sum / count > 4 skips division when count is zero.
Side effect
A side effect is a change such as modifying stored data or performing input or output during expression evaluation.
An increment changes a variable as a side effect, so two conflicting increments need a defined order.
Signed integer
A signed integer is a whole-number type that represents both negative and nonnegative values within a fixed range.
A signed integer can store a negative temperature count, but later additions must still fit its range.
Source code
Source code is the human-readable C text that a compiler translates into instructions used to build a program.
After changing the greeting text a program prints in its source code, rebuild the program before running the new version.
Standard input
Standard input is the program’s usual incoming text stream, often connected to the keyboard or redirected input.
A scanf call reads from standard input and converts the next matching characters into stored values.
Statement block
A statement block groups declarations and statements inside braces so they can serve as one controlled body.
A statement block keeps a message and a counter update together inside the same conditional branch.
Switch statement
A switch statement chooses an entry label by comparing one integer controlling value with fixed integer case values.
A switch statement can give the command characters lowercase r and uppercase R the same action.

T

Tolerance
A tolerance is an allowed numerical difference chosen for the scale and accuracy required by a comparison.
The program compares fabs(measured - target) with the tolerance instead of demanding exact equality.
Trace
A trace records the changing values and executed steps of a program so its behavior can be checked in order.
In a digit-sum loop, a trace of the remaining digits and running sum after each pass helps locate a wrong loop update.
Truncation toward zero
Truncation toward zero removes a number’s fractional part, making a negative result less negative in magnitude.
Truncation toward zero makes converting the representable value −7.9 to int give −7 instead of −8.
Two’s complement
Two’s complement is a signed binary representation in which the highest bit has negative weight.
In eight-bit two’s complement, the pattern 10000000 represents negative 128.

U

Undefined behavior
Undefined behavior occurs when a C program performs an operation for which the language imposes no requirements.
An overflowing signed product has undefined behavior even if one unoptimized run appears to wrap.
Unsigned integer
An unsigned integer represents only nonnegative whole numbers and uses modulo reduction when arithmetic exceeds its range.
An unsigned integer works well as a bit mask, but subtracting excessive stock can wrap to a huge value.
Unsigned wraparound
Unsigned wraparound is modulo reduction of an unsigned arithmetic result, mapping it back into the type’s nonnegative range.
Subtracting one from unsigned zero demonstrates unsigned wraparound by producing the maximum value.
Usual arithmetic conversions
The usual arithmetic conversions choose common operand types for many arithmetic and comparison operations.
The usual arithmetic conversions turn a negative int into unsigned when it is compared with unsigned int.

V

Variable
A variable is a named storage location with a declared type that determines how its stored value is interpreted.
The variable mass_kg stores a measurement while count records how many samples were entered.

W

Warning
A warning is a diagnostic that flags a likely problem while usually allowing the build to continue.
A format warning can reveal that %d was paired with a double argument, an invalid combination.
While loop
A while loop repeats its body while a condition is true, checking that condition before every body execution.
A digit-processing while loop stops once integer division has reduced the remaining value to zero.
Whitespace
Whitespace consists of characters such as spaces, tabs, and newlines that formats can skip or read depending on the conversion.
Numeric scanf conversions skip leading whitespace, but %c reads a leftover newline unless told to skip it.
Widening
Widening converts a value to a type with enough representable range before it participates in a risky calculation.
Widening a to long long before a * b changes the product’s type; casting after the product is too late.

About this lesson

By StudeliReport a mistake

Make your own Study Materials

Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.

Get Started