C concepts

One all time favourite topic is structures and bitfields.

Bitfield is wholly contained within its container.
A bitfield that does not fit in container it placed in next container of same type.

struct T1{ unsigned int a:12; unsigned int b;      unsigned int c:4;} __attribute__((packed));
struct __attribute__((__packed__)) T2{ unsigned int a:12; unsigned int b:32; unsigned int c:4;};

sizeof: T1: a=0:11, b=16:47, c=48:51, hence sizeof is 7 Bytes.
Logic: Since attribute is packet, the alignment will be byte-sized. Hence b is aligned on next byte boundary, i.e. bit 16.

sizeof: T2: a=0:11, b=12:43, c=44:47, hence sizeof is 6 Bytes.
Logic: Even though b is having same number of bits, but it is now bitfield and can start immediately after a.

If packed attribute is removed, then sizeof(T1)=sizeof(T2)=12 Bytes.

When aligned(x) is used: specifies a minimum alignment for the variable in bytes. It tells the compiler to put the variable on an address that's a multiple of x-bytes.
e.g. int x __attribute__ ((aligned (16))) = 0; //put x at 16-byte aligned address
struct S{ int a[3] __attribute__ ((aligned (8))); }; //create a double-word aligned int triplet

Note: x must be power of 2. Say, aligned(12) will generate compiler error: requested alignment is not a power of 2

struct T7{ unsigned int a:12; unsigned int b;      unsigned int c:4;} __attribute__((packed, aligned(X)));
struct __attribute__((__packed__, aligned(X)) T8{ unsigned int a:12; unsigned int b:32; unsigned int c:4;};
sizeof(T7)=sizeof(T8)=8 Byte. for X=4, 8. [X has to be power of 2].
What will be size for alignment X=2?? 
     sizeof(T7)=8 Byte. sizeof(T8)=6 Byte.

zero field
struct T3 {unsigned int x:5;                          unsigned int y:8; }; //alignment not forced
struct T4 {unsigned int x:5; unsigned int:0; unsigned int y:8; }; //alignment forced
sizeof T3: 4 Bytes, Sizeof T4 : 8 Bytes.

struct T9 {};    sizeof(T9): 0

struct T10 {char a[0];};    sizeof(T10): 0  //It is zero byte array.
struct T10 t10;  t10.a[0]=10;   printf("t10.a[0]: %d", t10.a[0]);  //Compiles. Outputs t10.a[0]: 10

Flexible Array Member:
struct T11 { int a; char b[]}; //sizeof T11: 4
It is used to append data of arbitrary size at runtime, quite useful in sending data over a socket. They are very useful as the last element of a structure which is really a header for a variable-length object. 
It has to be last member of struct, else compiler error: flexible array member not at end of struct.
It can not be lone member of struct, else compiler error: flexible array member in otherwise empty struct
Some Error Cases
struct A { int x:10; }; struct A a;
printf("%p ", &a.x); //compilation error: cannot take address of bit-field 'x'

struct B { unsigned int x[10]:5; } // compilation error: bit-field 'x' has invalid type

struct C { unsigned int y:33; } // compilation error: width of 'y' exceeds its type
=> Solution, make it long int y:33

union is similar to structure, but all members are stored at same address. Thus, members of union can be accessed once at a time. 
Why union? Designed to prevent memory fragmentation, as it creates standard size for certain data.
Compiler reserve enough room for the largest member

typedef union_u {    double PI;    int i;  }union_t;
union_t n;
n.PI = 3.14; printf("union PI: %f, int 0x%x\n", n.PI, n.i);
n.i = 0x32; printf("union PI: %f, int 0x%x\n", n.PI, n.i);
printf("union %p PI: %p, int %p\n", &n, &n.PI, &n.i);

Output:
union PI: 3.140000, int 0x51eb851f
union PI: 3.139999, int 0x32
union 0x7fff2b9cec10 PI: 0x7fff2b9cec10, int 0x7fff2b9cec10

typedef union_u {    double PI;    int i;  int j:10; }union_t;  union_t n;
printf("union PI: %f, int 0x%x j:%x\n", n.PI, n.i, n.j);
Output: union PI: 3.140000, int 0x51eb851f j:11f


Implement sizeof
struct name *p=(struct name*)0;
p++;
printf("sizeof struct name: %d\n", *p);

struct name {char a};  //warning: no semicolon at end of struct or union



Boring Conceptual Theory

Operator Precedence: My shorthand for remembering this is as below:

PUMA-SRE-B3-L2-CAC    [underline:Right To Left. Remaining: Left To Right]

  • Parenthesis Unary Multiplication Addition
  • Shift Relation Equality
  • Binary(3- ^,&,|) 
  • Logical (2- &&, ||)
  • Conditional (+=, ==, ^=, |=, &=, >>=, <<= etc.)  
  • Assignment (=)
  • Comma (,)


Data Types: char(1B), int(4B), float (4B), double(8B)
Qualifiers: short(2B), long(4B), signed, unsigned

Integral: int, short char, char, long, unsigned.

Pointer addition is not allowed, but same type pointer can be subtracted.
  • x+y: If either is pointer, other must be integral
  • x-y: If either is pointer, other must be integral or pointer of same base type
  • x=y: a, y can not be array
  • x op y: x, y can not be array or structure.
Variable names: a-z, A-Z, 0-9. 
Keywords as variable name not allowed: e.g. int, goto, 5a, max-wel.
main is valid variable name.


char const (e.g. 'x') is int. i.e. ASCII char const '0' (=48) different from '\0' (=0)
usage: atoi()  ////s[i]-'0': numeric value of character at s[i]
  n=0;  
  for(i=0;s[i]>='0' && s[i]<=9; i++){ n=10*n+(s[i]-'0');  } 
  return n;

ceil(1.54F)=2.000000, floor(1.54F)=1.000000

enum const: A way to associate const values with names
enum can contain same values, but names must be unique.

-1L < 1U  :  1U is promoted to signed long
-1L > 1UL : -1L promoted to unsigned long



  1. extern, static variables are initialized to 0 by default.
  2. modulus(%) operator does not work on float/double.
  3. The unary operators (e.g. ++) are only for variables, so illegal to say (i+j)++;
  4. x=1,y=2=>x&y=0(logical op)  x&&y=1 (Left->right evaluated for truth value
  5. *, /, %, -x: Not valid on pointers.

Following operations are undefined

  • printf("%d %d", ++i, i++);            
  • printf("%d %d", ++n, pow(2, n));  
  • a[i]=++i;   
  • i=i++;


for(e1, e2, e3) =>    e1;  while(e2){ ...; e3; }

a=f()+g()+h();  //Function f, g, h can be called in any order.

Value of object can be modified once between two sequence points.
  j=i++ * i++; // i modified twice between sequence point, illegal
  j=i++ && i++; // i modified once before and once after sequence point(&&)

Where does sequence point occur?

  • At the end of full expression
  • At &&, ||, ?: operators
  • Function Call (after evaluating all args just before function being called)

If an object to get modified within expression, all accesses to it within same expression must be to compute value to be stored in object.

  • a[i]==++i; //illegal. because access of i in a[i] has nothing to do with value that ends up being store in i. compiler may not know ehether to access before or after increment to i.
  • *p++ = c; //valid. Even if value of p is acessed twice, it is to modify two different objects of p, i.e. p and *p.
  • i=i+1; //valid as i is accessed to determine final value of i.

if(3==3.0F) TRUE

if(i%2 == j%3) error: lvalue required
#define printname(x) #x

int variable=10; printf("%s", printname(variable);
Output: variable.

i=printf("hi"); => i=2

printf("\n%*.*s", 10, 7, "c-pointer");//10 char wide, 7 char printed.
---c-point


a=1,3,5 => a=1
a=(1,3,5) => a=5
i=2+ (1,2,3,4,5) = 7  [Left to Right, evaluate 1,2,3,4 discard result)

x=55; printf("%d %d %d", x<=55, x=40, x>=10); => 1 40 1

int a=10,b=20,c=30;
printf("\n%d ", a++&&b++&&c++);   printf("%d %d %d", a,b,c);
1 11 21 31

a=10,b=20,c=30;
printf("\n%d ", a++||b++||c++);   printf("%d %d %d", a,b,c);
1 11 20 30


main(){ int main=10; printf("%d\n", main); } //no error, main is not keyword

extern int x;
main() { printf("%d", x);}
x=40 

Output: 40, and warning: data definition has no type or storage class
If we remove x=40, linker err 0.c:(.text+0x1a2): undefined reference to `xx'
int arr[]={10,20,30};   //
arr[2]=*(arr+2)=*(2+arr)=2[arr].
This, printf("%d", -2[arr]);  prints -30;

printf("%s", "hello" "world"); // prints helloworld


Some expressions:

  • 6-3<<2 12="" 3="" span="">
  • 5>=5 => true
  • *p++ is same as *(p++)  since ++ > * precedence
  • t=a<<2 nbsp=""> t= a<<(2+a) //precedence
  • unsigned int x=-5; //x=INT_MAX+1-5
  • unsigned char a=-64; //a=CHARMAX+1-64=192
  • No type conversion is done for evaluating &&, ||, =
  • ~11 = -(11+1)=-12    int y=~0; //y=-1
  • int x=~0;  %d x=-1,  %x x=0xFFFFFFFF
  • unsigned char x=0xFF; %d x=255 
  • signed char x=0xFF;     %d x=-1
  • unsigned short x=~0; %d x=65536
  • signed short x=~0;     %x x=0xFFFF
variable type: printed using %d and 0x%x
unsigned char x=0xFF;  printf("unsigned char x=0xFF: %d 0x%x", x, x);
signed char u=0xFF;      printf("\nsigned char u=0xFF: %d 0x%x", u, u);
unsigned short z=~0;    printf("\nunsigned short z=~0: %d 0x%x", z, z);
signed short zz=~0;      printf("\nsigned short zz=~0: %d 0x%x", zz, zz);
unsigned int ix=~0;       printf("\nunsigned int ix=~0: %d 0x%x", ix, ix);
unsigned char a=-64;    printf("\nunsigned char a=-64: %d 0x%x\n", a, a);
Output
unsigned char x=0xFF: 255 0xff
signed char u=0xFF: -1 0xffffffff
unsigned short z=~0: 65535 0xffff
signed short zz=~0: -1 0xffffffff
unsigned int ix=~0: -1 0xffffffff
unsigned char a=-64: 192 0xc0

  • x>>y: x and y are unchanged
  • ++(-i), int j= (-i)++, ++(-i), (-i)++ ++(-i) //all compilation error lvalue required as increment operand
Order of Type up-conversion:
CSIULDf: (char, short) -> int -> unsigned -> long -> double <- float="" font="" nbsp="">
Note reverse arrow for float. Float always upconverted to double efore expression is evaluated.


Reference

2 comments:

  1. Good blog.
    I guess your union example has some compilation error.
    Also output for this is not correct:
    numbers.i = 50; printf("union PI: %f, int 0x%x\n", n.PI, n.i);

    Kapil

    ReplyDelete
  2. Thank you Kapil for reading. I appreciate your time for checking it and spotting as well.
    Made correction in main content.

    Caution: There can be more such instances of code not building as main idea in initial days is to suggest approach with assumption that coding is left to reader.

    ReplyDelete