C Programming C++ Java JavaScript Kotlin PHP Python

πŸ’» C Programming

Learn the basics of C programming, including variables, loops, and functions.

Data Types & Variables::(Built-in data types, Signed vs unsigned,(Short, long, float, double), Variable declaration and initialization)

1) What is a data type?


A data type tells the compiler:

  • how much memory to reserve for a value, and

  • how to interpret the bits stored there (integer vs real vs character, etc.).

A variable is a named memory location of a particular data type that holds a value.


2) Built-in (fundamental) data types in C


Common built-in types and what they represent:

  • char β€” a single character (1 byte).

  • int β€” integer (typical β€œnatural” integer for the machine).

  • short (short int) β€” smaller-range integer.

  • long (long int) β€” larger-range integer.

  • long long (C99) β€” at least 64-bit integer.

  • float β€” single-precision floating-point (approx 6–7 decimal digits).

  • double β€” double-precision floating-point (approx 15–16 decimal digits).

  • long double β€” extended precision (implementation-dependent).

  • void β€” no value / incomplete type (used in void functions or void *).

Note: exact sizes (bytes) and ranges are implementation dependent. On many modern 64-bit Unix-like systems using the LP64 model (e.g., GCC on Linux x86_64) typical sizes are:

  • char = 1 byte

  • short = 2 bytes

  • int = 4 bytes

  • long = 8 bytes

  • long long = 8 bytes

  • float = 4 bytes

  • double = 8 bytes

  • long double = 16 bytes (often)

Always confirm with sizeof(type) for your target platform.

Safer fixed-width types: prefer <stdint.h> types when exact widths are needed:

  • int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t.


3) Signed vs Unsigned

  • Signed integers store negative and positive values (two's complement on almost all modern systems). Example types: int, short, long.

  • Unsigned integers store only non-negative values, giving a larger positive range for the same number of bits. Example: unsigned int, uint32_t.

Example ranges (conceptual) for an 8-bit type:

  • signed char: βˆ’128 .. 127

  • unsigned char: 0 .. 255

Important behaviours:

  • Mixing signed and unsigned in expressions can lead to surprising promotions β€” unsigned often β€œwins”, causing negative values to convert to large positive values.

    int a = -1; unsigned int b = 1; if (a < b) // a promoted to unsigned => huge positive -> condition false printf("a < b\n");
  • Avoid mixing signed and unsigned without explicit casts.


4) Short, Long, (and long long)

Modifiers adjust the width of integer types:

  • short (usually smaller than int) β€” short int or short.

  • long (usually >= int) β€” long int or long.

  • long long (at least 64 bits) β€” added in C99.

Example:

short s = 30000; int i = 100000; long l = 1000000000L; long long ll = 9223372036854775807LL;

Use suffix L, LL, U as needed in integer literals: 1234U, 1234L, 1234LL.


5) Float & Double (floating point)

  • float β€” 32-bit single precision (approx. 6–7 decimal digits).

  • double β€” 64-bit double precision (approx. 15–16 digits).

  • long double β€” extended precision (implementation-dependent).

Floating point is approximate: beware rounding & precision errors. Use double by default for better accuracy; float is used when memory/performance constraints require it (e.g., large arrays on embedded devices, GPUs).

Example:

float f = 3.14159f; // suffix f for float literals double d = 3.141592653589793; long double ld = 3.141592653589793238L;

6) Declaration vs Initialization

  • Declaration tells the compiler the variable name and type.

    int x; // declaration (no initial value)
  • Initialization gives the variable its first value at point of declaration.

    int x = 10; // declaration + initialization

Always initialize variables before use β€” uninitialized variables contain indeterminate values and cause undefined behaviour.

Multiple declarations:

int a, b, c; // three ints declared, uninitialized int x = 1, y = 2; // declared and initialized

7) Type Modifiers, Qualifiers, and Storage Classes

Type modifiers (adjust base type):

  • signed, unsigned β€” signedness

  • short, long, long long β€” width modifiers

Type qualifiers (specify properties of access/optimization):

  • const β€” value cannot be changed after initialization (compile-time enforcement).

    const int MAX_USERS = 100;
  • volatile β€” tells compiler value may change externally (hardware, ISR), so do not optimize away reads/writes.

    volatile int *reg = (volatile int *)0x40000000;
  • restrict (C99) β€” pointer qualifier promising no aliasing through that pointer (optimization hint).

Storage class specifiers (lifetime & linkage):

  • auto β€” automatic local variable (default for local variables). Rarely written explicitly.

  • static β€” for local: keeps value between function calls (internal linkage for globals if at file scope).

    static int counter = 0;
  • extern β€” declares a global variable defined elsewhere (linker will resolve).

    // in header.h extern int shared_value; // in one .c file: int shared_value = 42;
  • register β€” hint to store variable in a CPU register (mostly ignored by modern compilers).


8) Common examples (with printf format specifiers)

#include <stdio.h> #include <stdint.h> int main(void) { char c = 'A'; int i = -42; unsigned int u = 42u; short s = 30000; long l = 1000000000L; long long ll = 9223372036854775807LL; float f = 3.14f; double d = 2.718281828459045; printf("char: %c, int: %d, unsigned: %u\n", c, i, u); printf("short: %hd, long: %ld, long long: %lld\n", s, l, ll); printf("float: %f, double: %.12f\n", f, d); printf("int32_t: %d, uint32_t: %u\n", (int)INT32_C(1234), (unsigned)UINT32_C(1234)); return 0; }

Use the correct format specifiers:

  • %d / %i for int

  • %u for unsigned int

  • %hd for short

  • %ld for long

  • %lld for long long

  • %f for float/double (but printf promotes float to double)

  • %c for char

  • %s for strings

For <stdint.h> types use printf macros from <inttypes.h> like PRId32, PRIu64 for portability:

#include <inttypes.h> int32_t x = 1234; printf("x = %" PRId32 "\n", x);

9) Promotion and Conversion rules (brief)

  • Integer promotion: smaller integer types (char, short) are promoted to int in expressions.

  • When mixing types (e.g., int and double), usual arithmetic conversions will convert the narrower to the wider type (e.g., integer β†’ floating point).

  • Be careful with signed ↔ unsigned mixing β€” cast explicitly when you mean it.


10) Memory & Scope 

Heap (dynamic allocation):

malloc() -> pointer -> [ allocated block: bytes ]

Global / static area:

[.data] initialized globals [.bss] zero-initialized globals/static vars

11) Use cases & when to choose what

  • Use int for general-purpose integer arithmetic (unless you need exact width).

  • Use unsigned when you know value is non-negative and you need the larger positive range (but be cautious with arithmetic).

  • Use short for memory-constrained arrays of small integers (embedded).

  • Use long / long long for large integers (timestamps, big counters).

  • Use <stdint.h> fixed-width types (int32_t, uint64_t) when you need portability and predictable sizes (file formats, network protocols).

  • Use size_t for sizes/indices (result of sizeof, used by memory functions).

  • Use float for memory/compute constrained numerical arrays; use double for general numeric computations.

  • Use long double for extra precision in scientific computing when supported.


12) Best practices & style rules

  1. Always initialize variables.

    int count = 0;
  2. Prefer int for general integers and size_t for sizes and indexing.

  3. Use fixed-width types (<stdint.h>) for binary formats/networking.

  4. Avoid unnecessary use of unsigned for arithmetic calculations to prevent surprising conversions.

  5. Use const for values that should not change β€” it documents intent and enables compiler optimizations.

    const int MAX = 100;
  6. Limit variable scope β€” declare variables in the smallest scope needed (inside loops/functions).

  7. Use descriptive names (student_count, buffer_len) not a, b.

  8. Check for overflow/underflow especially when converting between types or performing arithmetic on boundaries.

  9. Use correct printf/scanf format specifiers or the inttypes.h macros for fixed-width types.

  10. Use compiler warnings (-Wall -Wextra) and static analyzers to catch risky type usage.

  11. Document assumptions (e.g., endianness, expected ranges).

  12. Prefer double over float unless memory/speed constraints demand float.

  13. Use volatile only when necessary (hardware registers, shared variables modified in signal handlers).


13) Common pitfalls (watch out)

  • Using uninitialized variables β†’ undefined behaviour.

  • Mixing signed and unsigned values in comparisons.

  • Assuming int is 32 bits on all platforms β€” use fixed-width types if needed.

  • Using float expecting exact decimal arithmetic β€” floating point is approximate.

  • Overflow of signed integers (undefined behaviour) versus unsigned overflow (wraps modulo 2ⁿ).

  • Using wrong printf specifier β€” causes runtime errors or incorrect output.


14) Quick reference table (typical / common sizes β€” confirm with sizeof)

TypeTypical size (bytes) on x86_64 LP64Notes
char1character, often signed or unsigned by default depends on implementation
short2short int
int4general integer
long8long int
long long8at least 64 bits (C99)
float4single-precision
double8double-precision
long double16 (often)extended precision; platform dependent
size_t8unsigned type for sizes (from <stddef.h>)

15) Small practical examples

Safe: fixed-width integer for binary data

#include <stdint.h> #include <stdio.h> int main(void) { uint32_t checksum = 0xFFFFFFFFU; checksum += 1; // wraps correctly (unsigned) printf("checksum = %" PRIu32 "\n", checksum); return 0; }

Avoid mixing signed/unsigned

#include <stdio.h> int main(void) { int neg = -1; unsigned int u = 1u; if (neg < u) // surprising: converts neg to unsigned -> large value -> false puts("neg < u"); else puts("neg >= u"); return 0; }

Floating point precision

#include <stdio.h> int main(void) { float a = 0.1f; if (a * 10 == 1.0f) // may fail due to rounding puts("equal"); else puts("not equal"); return 0; }

Use tolerance when comparing floats:

#include <math.h> #define EPS 1e-6 if (fabs((double)(a*10) - 1.0) < EPS) { /* equal */ }

Final quick checklist

  • sizeof(type) to confirm on your compiler/target.

  • Use <stdint.h> for portability where required.

  • Initialize variables.

  • Prefer const to document immutability.

  • Avoid signed/unsigned mixing.

  • Use double by default for floating calculations.

  • Limit scope, pick descriptive names, and enable compiler warnings.

❓

No quizzes yet

Quizzes to test your knowledge coming soon!

πŸ“š Recent Tutorials

Explore the latest tutorials added to our platform

Code copied to clipboard!