* pointer values and lvalues If you just declare a var: int i; // called "automatic" variables at run time, a space for the var will be allocated automatically on the stack and deallocated when needed. A FIXED address will be assigned, let's say, addr 20,000. printf("%d", i); // print content of i: can be ANY value printf("%d", &i); // print address of i (on stack): 20,000. Note, this addr cannot be changed in the scope of the function being executed. The addr 20000 is fixed at this point. All you can do is change the CONTENT of memory pointed to by addr 20,000. The addr of var you can assign to is sometimes called an "lvalue" (left side value). i = 30; // put the number 30, in memory addr 20,000 Alternatively, think of the entire memory of the system, from 0 to 4GB, as one giant array M[], you can access M[0] .. M[2^32-1]. That means that each time you're trying to refer to 'i', you can replace it with M[20,000], so think of above assignment as M[20,000] <- 30 // store value 30 into mem addr 20,000 Think of all pointers as just numbers representing memory addresses in this large M array. Note: integers in C are 4 byte long. sizeof(int)==4. So in reality, "i=30" would place the value "30" into a sequence of 4 consecutive bytes starting at addr 20,000 .. 20,003. sizeof(X) depends on platform, architecture, and even compilers. Also C standards. In the old 32 bit days: sizeof(char)==1 sizeof(short)==2 sizeof(int)==4 sizeof(long)==4 // but the spec said that sizeof(long)>=size(int) Historically, sizeof(long) has been 4 bytes for so long, that everyone assumed it. When 64 bit CPUs became popular (Digital Equipment Corporation, or DEC, "Alpha" chip in the 1990s, was first commercial, inexpensive 64-bit processor -- with modified OSs called Ultrix and OSF/1). DEC and Alpha, made sizeof(long) initially equal 8 bytes (64 bits). Lots of programs broke, even after compiling. Example int i = FOO; long l = BAR; i = l; // assign l to i -> bits above 4G values get truncated // similar short s; // 16 bits (values from 0..65535) int i = 65537; s = i; printf("%d", s); // print 1 because 65537 == 2^16 + 1 // note 2^16 == 65536 Then the community proposed to keep long as 4 bytes, but to create "new" types that truly indicate a 64-bit quantity: (a) long long x; (b) quad q; Some compilers didn't support this new syntax, and you had to deal with compiler syntax incompatibilities (lots of #ifdef's to resolve). Today: don't assume that a long will be 4 bytes, it can be 4 or 8 bytes. You can check at compiler/runtime using sizeof(long). A lot of code nowadays avoids these and instead uses size-named quantities (often defined is sys/types.h or the like): u8 a; // an 8-bit quantity, unsigned u16 b; // same 16 bits u32 b; // same 32 bits u64 b; // same 64 bits u128 b; // same 128 bits (modern Intel/etc. CPUs support internal 128-bit // instructions, eg to encrypt or compute hashes) i8 x; // same as u8, but signed quantity Note: sizeof(any pointer) is always going to be the same length as the base architecture and processor memory bus width. void *p; // sizeof(p) is 4B on 32-bit CPUs, and 8B or 64-bit CPUs char *s; // same as p: doesn't matter what kind of pointer it is! float ***matrix; // sizeof(matrix) is also 4B or 8B * NEXT use after free buf over/underflows initialization redzones mem poisoning