Variables, printf & scanf
The types, formatted I/O, strings, and comparisons you need to finish Lab 1
Based on content from Dr. Stu Steiner, Eastern Washington University.
In a nutshell
In CSCD 210, you typed Scanner and System.out.println without thinking about how they worked. In C, you write scanf and printf directly, and the small set of traps that come with them is what separates “the program compiles” from “the program runs correctly.” This lesson covers every type Lab 1 uses, every scanf pattern Lab 1 uses, how strings work in C, and how to compare them without the == mistake.
Practice this topic: C Variables drill, or browse the practice gallery.
After this lesson, you will be able to:
- Declare
int,double,char, and fixed-size character arrays in C90 - Read input with
scanffor every type Lab 1 uses, and explain when&is needed and when it is not - Print formatted output with
printf, including doubles to a given number of decimal places - Compare C strings correctly with
strcmpand explain why==does not work - Avoid the integer-division trap, the
%fvs%lftrap, and the “char after int” whitespace trap - Name the CWE behind each of those traps so the Lab 1 reflection looks like review
Quick reference for Lab 1
Use Ctrl-F to find what you need.
| Goal | C code | Notes |
|---|---|---|
| Declare an integer | int age; |
top of block in C90 |
| Declare a floating-point value | double gpa; |
default to double, not float |
| Declare a single character | char standing; |
|
| Declare a string | char name[50]; |
size required; no separate String type |
| Prompt (no newline) | printf("Enter your age: "); |
printf never adds \n |
| Print text and a newline | printf("Name: %s\n", name); |
%s means string |
Print an int |
printf("Age: %d\n", age); |
%d or %i |
Print a double, two decimals |
printf("GPA: %.2f\n", gpa); |
%.2f means two places |
Print a single char |
printf("Standing: %c\n", standing); |
%c means one character |
Read an int |
scanf("%d", &age); |
& required |
Read a double |
scanf("%lf", &gpa); |
%lf in scanf; & required |
| Read a word | scanf("%s", name); |
no &; array name is already an address |
| Read one char after a number | scanf(" %c", &standing); |
leading space skips whitespace |
| Compare strings | strcmp(name, "Alice") == 0 |
needs #include <string.h> |
| String length | strlen(name) |
returns size_t; use %zu or cast to (int) |
C90 for loop |
int i; |
counter declared at the top of the block |
| Block comment | /* ... */ |
only form legal under -std=c90 |
Coming from CSCD 210
You already know declarations, primitive types, formatted output, reading input, if/else, for loops, and calling methods. In Java those were System.out.printf, Scanner.nextInt(), and friends. The concepts map over directly; the syntax is new, and three traps are specific to C: %lf (not %f) for double in scanf, no & on arrays, and a leading space before %c after reading a number. The rest of this page walks through each one.
Declaring data
Types
| C type | Typical size | What it holds | printf |
scanf |
|---|---|---|---|---|
int |
4 bytes | whole numbers, signed | %d (or %i) |
%d |
double |
8 bytes | floating-point, default | %f or %.2f |
%lf |
float |
4 bytes | smaller floating-point, rarely useful | %f |
%f |
char |
1 byte | a single character ('A') |
%c |
%c |
short |
2 bytes | smaller whole number | %hd |
%hd |
long |
8 bytes | larger whole number | %ld |
%ld |
char name[N] |
N bytes | fixed-size string buffer | %s |
%s |
Use double, not float, for the same reason CSCD 210 did: float trades precision for nothing you need.
%d and %i both mean “decimal int” in printf, so pick whichever you prefer there. In scanf, stick to %d: %i accepts hex and octal prefixes, which is not usually what you want.
Declarations at the top (C90)
In C90, every declaration must come before the first statement of its enclosing block.
int main(void)
{
/* declarations first */
int age;
double gpa;
char standing;
char name[50];
int i; /* for loop counter lives here too */
/* statements after */
printf("Enter your age: ");
scanf("%d", &age);
/* ... */
return 0;
}
Mixing declarations and statements, including for (int i = 0; ...) inline, is a C99 feature. Lab 1 is graded with -std=c90 -pedantic and will refuse it.
Uninitialized variables hold whatever was there
int x; /* reserves 4 bytes but does not set them to 0 */
printf("%d\n", x); /* prints whatever was in that memory. Could be 0, could be 32761. */
Java zero-initialized your fields (and refused to compile if you read a local before assigning to it). C does neither. The fix is to initialize at declaration (int x = 0;) or to write to the variable through an assignment or a successful scanf before reading it. -Wall -Wextra catches some of these at compile time; the debugger catches the rest.
Arithmetic traps
Integer division truncates toward zero. 5 / 2 is 2, not 2.5. -7 / 2 is -3, not -4.
int a = 7;
int b = 2;
printf("%d\n", a / b); /* 2 */
printf("%.2f\n", (double)a / b); /* 3.50 */
printf("%.2f\n", a * 5.0 / 4.0); /* full precision; classic Lab 1 pattern */
printf("%.2f\n", a * (5 / 4)); /* 7.00, because 5/4 evaluates to 1 first */
The last line is the classic Lab 1 trap. For “GPA on 5.0 scale,” write gpa * 5.0 / 4.0. Writing gpa * (5 / 4) silently gives gpa * 1.
Zero is false. Every other number is true. C90 has no separate boolean type. if tests an integer; zero counts as false, any non-zero value as true.
int x = -7;
if (x) printf("yes\n"); /* prints. -7 is non-zero, so true. */
if (0) printf("never\n"); /* never prints. */
Null pointers and 0.0 also count as false in any truth test. In C99, <stdbool.h> defines bool, true, false, but underneath they are still integer 1 and 0.
= assigns, == compares. Confusing them compiles: = is a legal expression in C. if (age = 18) assigns 18 to age, evaluates to 18, which is non-zero, which is true. The if body always runs. This is CWE-481; -Wall warns on it unless you wrap the assignment in double parens.
The four meanings of &
The ampersand is overloaded. You meet all four this quarter; Lab 1 uses the first and the third.
| Usage | Meaning | Example | Seen in |
|---|---|---|---|
Unary &x |
Address-of | scanf("%d", &age) |
this lesson |
Binary a & b |
Bitwise AND | flags & 0x01 |
Operators |
Binary a && b |
Logical AND | age >= 18 && age <= 65 |
this lesson, below |
int& (declaration) |
C++ reference | not used in C | never in this course |
Reading and writing
printf
printf writes a format string to standard output. Every % specifier gets replaced by the matching argument. printf never adds a newline; you put \n in the format string yourself.
int age = 20;
double gpa = 3.72;
char initial = 'J';
printf("Age: %d\n", age); /* Age: 20 */
printf("GPA: %.2f\n", gpa); /* GPA: 3.72 */
printf("Initial: %c\n", initial); /* Initial: J */
printf("Name: %s\n", "Alice"); /* Name: Alice */
printf("%d and %.2f\n", age, gpa); /* 20 and 3.72 */
printf("50%%\n"); /* 50% (escape literal %) */
Sidebar: diagnostics on stderr. There are two output streams: stdout for normal output and stderr for error messages. Use fprintf(stderr, "...") for diagnostics so they do not pollute output pipelines:
if (meter_end < meter_begin) {
fprintf(stderr, "warning: meter_end < meter_begin\n");
}
Lab 1 does not require stderr, but Quiz 1 asks about it.
scanf: the four patterns Lab 1 needs
int age;
double gpa;
char standing;
char name[50];
scanf("%d", &age); /* 1. int: & required */
scanf("%lf", &gpa); /* 2. double: %lf in scanf, & required */
scanf("%s", name); /* 3. word: no & (array is already an address) */
scanf(" %c", &standing); /* 4. char after a number: LEADING SPACE */
Why %d and %lf need &. C passes arguments by value. To modify your variable, scanf needs the variable’s address, and &age is how you ask for it. For the full machine-level story of what the address actually is, see How & and pointers actually work.
Why scanf("%s", name) does not use &. The name of an array in C is automatically a pointer to its first element (the deep-dive page calls this “array-to-pointer decay”). name already is an address; &name is a different type and will warn.
Why " %c" needs the leading space. Every scanf conversion except %c silently skips leading whitespace. %c does not. After scanf("%d", &age) reads 42, the \n the user pressed is still in the input buffer. A following scanf("%c", &standing) would read that newline into standing. The one-character fix:
scanf("%d", &age);
scanf(" %c", &standing); /* the space says "skip whitespace, then read one char" */
%f vs %lf in scanf. float is 4 bytes; double is 8. In printf this rarely matters because float is promoted to double before a variadic call. In scanf, no promotion happens; the specifier must match the pointer’s type exactly. Writing scanf("%f", &my_double) stores 4 bytes into an 8-byte slot and corrupts the value with zero warning. Always %lf for double in scanf.
Check your understanding (what are the bugs?)
A student writes:
double gpa;
char standing;
int age;
printf("Enter your age: ");
scanf("%d", &age);
printf("Enter your GPA: ");
scanf("%f", &gpa);
printf("Enter your class standing: ");
scanf("%c", &standing);
The program compiles with no warnings (the student turned them off to “clean up”), but at runtime the class-standing prompt never waits for input and standing ends up as '\n'. The GPA also looks wrong when printed later. What are the three distinct bugs?
Reveal answer
%fshould be%lf.gpais adouble.%cshould be" %c"(leading space). The newline left over fromscanf("%f", ...)gets read intostandingwithout it.- Warnings should not be turned off.
-Wall -Wextrawould have flagged the%fmismatch. Zero-warnings is the Lab 1 grading target.
Strings in C are character arrays
There is no String class in C. A “string” is an array of char ending with a null byte '\0'. Every string function scans forward until it hits that zero byte.
char name[50]; /* reserve 50 bytes; 49 usable + null */
scanf("%s", name); /* reads one word; writes '\0' at the end */
printf("Hello, %s!\n", name); /* prints until '\0' */
Key points for Lab 1:
- Size is required at the declaration.
char name[50]reserves 50 bytes, so 49 usable characters plus one for the terminator. scanf("%s", ...)stops at whitespace. It reads one “word.” Lab 1’s inputs are single words.- No bounds checking by default. If the user types more characters than the array can hold,
scanf("%s", name)keeps writing past the end. That is CWE-120. The width-capped formscanf("%49s", name)stops after 49 characters and leaves room for'\0'. Lab 1 does not require the cap, but the reflection asks you to explain it. - Never use
gets.gets(buf)has no length argument; it was removed from C11 entirely. Usefgets(buf, sizeof(buf), stdin)for whole-line reads.
For the historical reason C strings look this way (BCPL heritage, PDP-11 register pressure), see Why C strings are null-terminated.
Comparing strings: use strcmp, not ==
Because a string is an address, str1 == str2 compares the two addresses, not the contents. Two arrays holding the same text compare as not equal. That is CWE-597.
The correct tool is strcmp from <string.h>:
#include <string.h>
if (strcmp(lang, "C") == 0) {
printf("Excellent choice.\n");
} else if (strcmp(lang, "Java") == 0) {
printf("We are upgrading you to C this quarter.\n");
} else {
printf("Interesting choice.\n");
}
strcmp returns 0 when equal, a negative value when the first sorts before the second, and a positive value when it sorts after. Zero means equal: memorize it. The idiom is strcmp(a, b) == 0 for equality. Never write strcmp(a, b) == 1: the standard promises only the sign, not the specific value.
strlen(s) returns the number of characters before '\0'. Its return type is size_t; print it with %zu or cast to (int).
Deciding and looping
if/else and for work the same way they did in Java. Three small differences in C90:
if / else if / else
if (age > 18) {
printf("You are an adult.\n");
} else if (age == 18) {
printf("You just became an adult.\n");
} else {
printf("You are a minor.\n");
}
Braces are optional for a single-statement body. Always use them anyway. Missing braces on a multi-statement body is CWE-483; you will see the full Apple “goto fail” story in If/Else, Switch & Loops and in the CVE deep-dive.
Logical OR for case-insensitive comparisons
if (standing == 'F' || standing == 'f') {
printf("Standing: Freshman\n");
} else if (standing == 'O' || standing == 'o') {
printf("Standing: Sophomore\n");
}
|| is logical OR. | (single pipe) is bitwise OR: different operation, different result. Same distinction with && vs &.
for loop in C90 (counter at the top)
int i; /* declared at the top of main with the other variables */
for (i = 1; i <= 5; i++) {
printf("In %d year(s) you will be %d\n", i, age + i);
}
In C99 you can write for (int i = 1; ...) and declare the counter inside the loop. Lab 1 compiles with -std=c90 -pedantic, so you cannot.
Lab 1 pitfalls and security
Common mistakes
| You wrote | What C does | Fix |
|---|---|---|
scanf("%f", &gpa) for double |
writes 4 bytes into an 8-byte slot | scanf("%lf", &gpa) |
scanf("%s", &name) |
wrong type; warning | scanf("%s", name) |
scanf("%c", &standing) after a number |
reads the leftover '\n' |
scanf(" %c", &standing) |
printf("Name: %s", name) |
no newline | printf("Name: %s\n", name) |
// my comment under -std=c90 |
-pedantic error |
/* my comment */ |
int months = age * 12; mid-block |
declaration mixed with statements | declare at top |
for (int i = 1; ...) |
C99 | int i; at top, for (i = 1; ...) |
if (lang == "C") |
compares addresses, not contents | strcmp(lang, "C") == 0 |
strcmp(a, b) == 1 |
non-portable | strcmp(a, b) == 0 for equality |
gpa * (5 / 4) |
5/4 is 1 |
gpa * 5.0 / 4.0 |
strcmp without header |
implicit declaration warning | #include <string.h> |
if (age = 18) |
assigns 18, always true | if (age == 18) |
The five CWEs behind the reflection questions
Each trap has a matching entry in the Common Weakness Enumeration. You do not need to memorize the numbers; the reflection walks you through each one.
| Reflection Q | CWE | What went wrong | Where above |
|---|---|---|---|
| Q1 | CWE-481 | if (x = 1) instead of if (x == 1) |
”= assigns, == compares” |
| Q2 | CWE-597 | str1 == str2 instead of strcmp |
“Comparing strings” |
| Q3 | CWE-483 | No braces on a multi-line if body |
“Always use them anyway” |
| Q4 | CWE-134 | printf(user_input) as format string |
see deep-dive |
| Q5 | CWE-120 | scanf("%s", buf) with no length cap |
“No bounds checking by default” |
For each CWE’s concrete exploit mechanics and the historic CVE it produced (Morris Worm, Baron Samedit, Apple goto fail, the WhatsApp GIF overflow), see The Lab 1 CWEs, with exploit mechanics.
Check your understanding (multiple correct)
- B is wrong:
nameis already an address;&nameis a different type and will warn. - E is wrong:
fgets(buf, N, stream)reads at mostN − 1characters, plus the null terminator. WithN = 100you get 99 readable characters.
What comes next
You can now declare, read, print, and compare every value Lab 1 asks for. Operators and Control Flow go deeper. You already have enough to write the whole Lab 1 program. Open lab1.c and translate Lab1.java section by section. Want to drill this page first? C Variables or the practice gallery.