* Re-entrant Code
Re-entrant code could be used by multi-thread and the result is only dependent upon input parameters. Re-entrant code is thread safe but thread safe might not re-entrant since the state of code might change due to global states. Re-entrant functions are similar to those of functional programming.
* Non Re-entrant Code
- Uses non-const global variables
- Uses static global/local variables
- Calls non re-entrant functions, like malloc, free, printf, fopen, and other I/O std functions
Monday, August 13, 2007
Interrupt Service Routine (ISR)
* ISR Issues
Find out what is wrong in the following ISR:
__interrupt double compute_area(double radius)
{
     double area = PI * radius * radius;
     printf("\n area = %f\n", area);
     return area;
}
ISR is supposed to be
- No input parameters
- No returns
- Be compact and simple. Note: float point arithmetic operations and printf are complex and not re-entrant.
Find out what is wrong in the following ISR:
__interrupt double compute_area(double radius)
{
     double area = PI * radius * radius;
     printf("\n area = %f\n", area);
     return area;
}
ISR is supposed to be
- No input parameters
- No returns
- Be compact and simple. Note: float point arithmetic operations and printf are complex and not re-entrant.
Const and Volatile Qualifier
* Const
- Read only
- Initialization
const type must be initialized when it is declared.
     const int j; /* error */
- Specify the exact data type
     #define i 10
     const long j = 10;
     char h = i;
     char k = j; /* error due to truncation */
- Save memory
Memory is allocated once.
     #define STRING "abcdefg"
     const char string[] = "abcdefg";
     printf(STRING); /* first time allocation */
     printf(string);
     printf(STRING); /* second time allocation */
     printf(string);
- Change const
     const int i = 0;
     int *p = (int *)&i;
     *p = 10;
* Volatile
- Usage
-- Hardware registers or ports which might be changed by I/O
-- Non-automative variables in ISR which might be changed by ISR
-- Global variables in multi-threading environment which might be changed by other thread.
- Effect
Compiler would re-read this variable from the cache or memory instead of register.
* Qualifier Meaning
- int const/volatile *p = &i;
- int * const/volatile p = &i;
- int volatile * const p = &i;
* Declaration of Const and Volatile
- Two types of declaration
     const/volatile int i; <=> int const/volatile i;
The latter one might be better. Think about this one:
     typedef char * pchar;
     const pchar p;
It might be explained as "const char *" but actually it is "char * const". The declaration of "pchar const p" has no such confusion.
- Declaring an entire object to be volatile and/or const effectively
declares each member of that object as volatile and/or const.
- Defining a data type to be const and/or volatile might be more useful than just defining an object of this type to be const and/or volatile. The reason of this is no need to consider the type match (as explained below) when pointer assignments happen, like parameter passing in function calling. Otherwise, all these occurances should be declared as const and/or volatile.
Note: typedef struct A const AA; =>
AA is const but struct A is not. So use this format:
typedef struct A
{
...
} const B;
* Const/Volatile Pointer Assignment
     int *p = &i;
     int const *cp; /* No initialization is allowed since cp is a pointer not const pointer actually */
     p = cp; /* error */
     cp = p;
In the above example, cp is a pointer to a qualified data type while p is a pointer to a unqualified data type. In the assignment, the data type to which the left pointer points should be with the qualifier of the right one. So cp could not be assigned to p.
     int **pp = &p;
     int const **cpp;
     cpp = pp; /* error */
which might happen in the parameter passing of function calling. Why?
pp => a pointer to a pointer to int
cpp => a pointer to a pointer to const int
"A pointer to int" is not the same data type as "a pointer to const int". Therefore, pp and cpp is different pointers. Casting needed here. If
pp => a pointer to a pointer to int (int **)
cpp => a pointer to a const pointer to int (int * const *) or
           a const pointer to a const pointer to int (int * const * const) or
           a const pointer to a pointer to int (int ** const)
then cpp = pp; is legal.
- Read only
- Initialization
const type must be initialized when it is declared.
     const int j; /* error */
- Specify the exact data type
     #define i 10
     const long j = 10;
     char h = i;
     char k = j; /* error due to truncation */
- Save memory
Memory is allocated once.
     #define STRING "abcdefg"
     const char string[] = "abcdefg";
     printf(STRING); /* first time allocation */
     printf(string);
     printf(STRING); /* second time allocation */
     printf(string);
- Change const
     const int i = 0;
     int *p = (int *)&i;
     *p = 10;
* Volatile
- Usage
-- Hardware registers or ports which might be changed by I/O
-- Non-automative variables in ISR which might be changed by ISR
-- Global variables in multi-threading environment which might be changed by other thread.
- Effect
Compiler would re-read this variable from the cache or memory instead of register.
* Qualifier Meaning
- int const/volatile *p = &i;
- int * const/volatile p = &i;
- int volatile * const p = &i;
* Declaration of Const and Volatile
- Two types of declaration
     const/volatile int i; <=> int const/volatile i;
The latter one might be better. Think about this one:
     typedef char * pchar;
     const pchar p;
It might be explained as "const char *" but actually it is "char * const". The declaration of "pchar const p" has no such confusion.
- Declaring an entire object to be volatile and/or const effectively
declares each member of that object as volatile and/or const.
- Defining a data type to be const and/or volatile might be more useful than just defining an object of this type to be const and/or volatile. The reason of this is no need to consider the type match (as explained below) when pointer assignments happen, like parameter passing in function calling. Otherwise, all these occurances should be declared as const and/or volatile.
Note: typedef struct A const AA; =>
AA is const but struct A is not. So use this format:
typedef struct A
{
...
} const B;
* Const/Volatile Pointer Assignment
     int *p = &i;
     int const *cp; /* No initialization is allowed since cp is a pointer not const pointer actually */
     p = cp; /* error */
     cp = p;
In the above example, cp is a pointer to a qualified data type while p is a pointer to a unqualified data type. In the assignment, the data type to which the left pointer points should be with the qualifier of the right one. So cp could not be assigned to p.
     int **pp = &p;
     int const **cpp;
     cpp = pp; /* error */
which might happen in the parameter passing of function calling. Why?
pp => a pointer to a pointer to int
cpp => a pointer to a pointer to const int
"A pointer to int" is not the same data type as "a pointer to const int". Therefore, pp and cpp is different pointers. Casting needed here. If
pp => a pointer to a pointer to int (int **)
cpp => a pointer to a const pointer to int (int * const *) or
           a const pointer to a const pointer to int (int * const * const) or
           a const pointer to a pointer to int (int ** const)
then cpp = pp; is legal.
Sunday, August 12, 2007
Casting in C
* The cast operator forces the conversion of its SCALAR operand to a specified SCALAR data type, or to void . The operator consists of a type-name, in parentheses, that precedes an expression, as follows:
( type-name ) expression
The type-name can also be an enum specifier, or a typedef name. The type-name can be a structure or union only if it is a pointer. That is, the type-name can be a pointer to a structure or union, but cannot be a structure or union because structures and unions are not scalar types. For example:
     (struct abc *)x /* allowed */
     (struct abc)x /* not allowed */
Cast operations cannot force the conversion of any expression to an array, function, structure, or union. The following example casts the identifier P1 to pointer to array of int:
     (int (*)[10]) p;
This kind of cast operation does not change the contents of P1 ; it only causes the compiler to treat the value of p as a pointer to such an array.
     p + 1; /* Increments by 10*sizeof(int) */
* Cast operators can be used in the following conversions that involve pointers:
- A pointer can be converted to an integral type. A pointer occupies the same amount of storage as objects of type int or long (or their unsigned equivalents). Therefore, a pointer can be converted to any of these integer types and back again without changing its value. No scaling takes place, and the representation of the value does not change. Converting from a pointer to a shorter integer type is similar to converting from an unsigned long type to a shorter integer type; that is, the high-order bits of the pointer are discarded. Converting from a shorter integer type to a pointer is similar to the conversion from a shorter integer type to an object of unsigned long type; that is, the high-order bits of the pointer are filled with copies of the sign bit.
- A pointer to an object or incomplete type can be converted to a pointer to a different object or a different incomplete type. The resulting pointer might not be valid if it is improperly aligned for the type pointed to. For example,
     char c[10];
     int *p = (int *)c[1]; /* misalignment */
It is guaranteed, however, that a pointer to an object of a given alignment can be converted to a pointer to an object of the same alignment or less strict alignment, and back again. The result is equal to the original pointer. (An object of character type has the least strict alignment.) For example,
struct A
{
     struct B;
     int C;
} a, *pa, *pa2;
pa = &a;
struct B *pb;
pb = (struct B *)pa;      /* Allowed and safe. Recall the alignment property of struct */
pa2 = (struct A *)pb;     /* Now pa == pa2 */
- A pointer to a function of one type can be converted to a pointer to a function of another type and back again; the result is equal to the original pointer. If a converted pointer is used to call a function that has a type not compatible with the type of the called function, the behavior is undefined.
Reference: EETime Embedded
( type-name ) expression
The type-name can also be an enum specifier, or a typedef name. The type-name can be a structure or union only if it is a pointer. That is, the type-name can be a pointer to a structure or union, but cannot be a structure or union because structures and unions are not scalar types. For example:
     (struct abc *)x /* allowed */
     (struct abc)x /* not allowed */
Cast operations cannot force the conversion of any expression to an array, function, structure, or union. The following example casts the identifier P1 to pointer to array of int:
     (int (*)[10]) p;
This kind of cast operation does not change the contents of P1 ; it only causes the compiler to treat the value of p as a pointer to such an array.
     p + 1; /* Increments by 10*sizeof(int) */
* Cast operators can be used in the following conversions that involve pointers:
- A pointer can be converted to an integral type. A pointer occupies the same amount of storage as objects of type int or long (or their unsigned equivalents). Therefore, a pointer can be converted to any of these integer types and back again without changing its value. No scaling takes place, and the representation of the value does not change. Converting from a pointer to a shorter integer type is similar to converting from an unsigned long type to a shorter integer type; that is, the high-order bits of the pointer are discarded. Converting from a shorter integer type to a pointer is similar to the conversion from a shorter integer type to an object of unsigned long type; that is, the high-order bits of the pointer are filled with copies of the sign bit.
- A pointer to an object or incomplete type can be converted to a pointer to a different object or a different incomplete type. The resulting pointer might not be valid if it is improperly aligned for the type pointed to. For example,
     char c[10];
     int *p = (int *)c[1]; /* misalignment */
It is guaranteed, however, that a pointer to an object of a given alignment can be converted to a pointer to an object of the same alignment or less strict alignment, and back again. The result is equal to the original pointer. (An object of character type has the least strict alignment.) For example,
struct A
{
     struct B;
     int C;
} a, *pa, *pa2;
pa = &a;
struct B *pb;
pb = (struct B *)pa;      /* Allowed and safe. Recall the alignment property of struct */
pa2 = (struct A *)pb;     /* Now pa == pa2 */
- A pointer to a function of one type can be converted to a pointer to a function of another type and back again; the result is equal to the original pointer. If a converted pointer is used to call a function that has a type not compatible with the type of the called function, the behavior is undefined.
Reference: EETime Embedded
Saturday, August 11, 2007
void and void *
* void
- NO INSTANCE of void data type, like "void a". The data type of void is abstract, like the abtract class in C++.
- void Usage: function return is void and function arguments are void
* "void *"
- The pointer of void * could be assigned by any type of pointer. No casting needed. However, it is not correct to assign the pointer of void * to the pointer of other data types. Casting needed.
- The pointer of void * could not perform arithmetic operations, like
void *p;
p++;    /* No increment is allowed on p */
- The pointer of void * could be used as an argument or return value of functions which accepts or return a pointer to any data type, respectively. For example,
void *memcpy(void *dst, void *src, size_t len);
- NO INSTANCE of void data type, like "void a". The data type of void is abstract, like the abtract class in C++.
- void Usage: function return is void and function arguments are void
* "void *"
- The pointer of void * could be assigned by any type of pointer. No casting needed. However, it is not correct to assign the pointer of void * to the pointer of other data types. Casting needed.
- The pointer of void * could not perform arithmetic operations, like
void *p;
p++;    /* No increment is allowed on p */
- The pointer of void * could be used as an argument or return value of functions which accepts or return a pointer to any data type, respectively. For example,
void *memcpy(void *dst, void *src, size_t len);
Struct and Union
* Typical Usage of Struct and Union
struct A
{
     int a;
     char b;
};
struct B
{
     short c;
     char d[2];
};
struct C
{
     int e;
     long f;
     short g;
};
struct CommonPacket
{
     int PacketType;
     union
    {
         struct A PacketA;
         struct B PacketB;
         struct C PacketC;
    }
}
In general, struct could be used effectively to describe a section of continual memory slots or registers; and access it with the pointer to the head of this section.
* Alignment in Struct (How to estimate the size of struct?)
- "Although you can never be absolutely sure how your compiler will pad the members within a structure, the Standard guarantees there will be no padding before the first member. The Standard also mandates that each member in a structure must be allocated in the order in which it's declared."
- Natural Alignment:
By default, each data member is aligned based on the size of its data type. The padding after this member is the one which makes the next data member aligned on its boundary. In the end, the size of struct should be multiple of the maximal size among data member in struct. This maximal size is just the alignment size for this struct. Fox example,
struct A
{
     char a;
     long b;
};
struct B
{
     short c;
     struct A d;
}
Then for struct A, a would be aligned with the size of 1 byte (sizeof(char)). Since the alignment size of b is 4 bytes (sizeof(long int)), three bytes need to be padded after a in order to guarantee b aligned in one address of multiple of 4. The final size of struct A is 8 which is multiple of 4. Therefore the alignment of struct A is 4. (Think about what if the positions of a and b switch in struct A.) About struct B, it contains one compound of struct A. So first the alignment of this compound should be considered. It is 4 as explained before. Two bytes are padded after c and the final size of struct B is 12 which satisfies the requirement.
Note: 1), The final size of struct is not equal to (N x Len), where N is the number of data members and Len is the maximal size of data members. The objective is to save memory allocation as much as possible. Look at this example:
struct C
{
char x1;
short x2;
int x3;
char x4;
};
The size of struct C is not 16.
2), For arrays, the alignment size is the size of data type but not the size of the array. For example,
long int c[20];
The alignment size for c is sizeof(long) but not sizeof(c).
- Alignment with #pragma
Force structs to align n bytes: #pragma pack(n)
Cancel alignment of n bytes: #pragma pack()
The alignment size of each data member should be the minimal value between its natural alignment size and n.
In summary, 1) defining the alignment size for each data member (compounds first), 2) align data members in sequence, 3) save memory as much as possible.
- Offset Calculation
(size_t)((char *)&((struct A *)0)->f - (char *)((struct A *)0))
* Initialization of struct
- struct A a = {'t', 'c', 8, 0.99, "example"}; or
struct A a = {0}; /* Every member is 0 now, no matter which type.*/
* Assignment of struct
struct A
{
     char *p;
     char c;
} a, b;
char cc = 'c';
a.p = &cc;
a.c = 15;
b = a;
*b.p = 30;      /* cc now is changed */
If the pointer is contained in struct, when assignment happens between two variables, two pointers are point to the same memory.
Although arrays could not be assigned to each other, they could if they are within one struct, like this:
struct A
{
     char array[10];
} a, b;
for (int i = 0; i < 10; ++i)
     a.array[i] = i;
b = a;     /* b.array now is the same with a.array */
* Struct For Bit Map
Under some circumstances, struct could be used to do bit map for a block of memory, like this
struct A
{
     int a:1;
     int b:7;
} t;
t.a = 1;
t.b = 0x7f;
- The total number of bits should be reasonable. It might be the size of one of basic data types, like char, short, int, long int, etc.
- Be cautious that t.a and t.b are defined as SIGNED int. Therefore, one bit needs to be the sign and the value ranges of them are [0,-1] and [-64, 63]. Unsigned data type might be more useful for this kind of struct usage since each bit in this struct should be meaningful.
- Almost everything of bit field in struct is implementation-dependent. Make sure everything, like which end starts in bit order, whether it allows cross the boundary of byte, etc. before use this data structure.
* Union in Memory
In general, all data members of one union start at the same low memory address. This property would be used to exploit some special usages of union.
union bits32
{
     char bytes[4];
     int whole;
} t;
t.whole = 0x12345678;
t.bytes[0] = 0x90; => Now t.whole becomes 0x12345690 in little endian system.
Another classic example of union to check system endian:
t.whole = 1;
return (t.bytes[0] == 1); /* True is little endian and false is big endian */
Or:
union bits32 endian_test = { { 'l', '?', '?', 'b' } };
#define ENDIANNESS ((char)endian_test.whole)
struct A
{
     int a;
     char b;
};
struct B
{
     short c;
     char d[2];
};
struct C
{
     int e;
     long f;
     short g;
};
struct CommonPacket
{
     int PacketType;
     union
    {
         struct A PacketA;
         struct B PacketB;
         struct C PacketC;
    }
}
In general, struct could be used effectively to describe a section of continual memory slots or registers; and access it with the pointer to the head of this section.
* Alignment in Struct (How to estimate the size of struct?)
- "Although you can never be absolutely sure how your compiler will pad the members within a structure, the Standard guarantees there will be no padding before the first member. The Standard also mandates that each member in a structure must be allocated in the order in which it's declared."
- Natural Alignment:
By default, each data member is aligned based on the size of its data type. The padding after this member is the one which makes the next data member aligned on its boundary. In the end, the size of struct should be multiple of the maximal size among data member in struct. This maximal size is just the alignment size for this struct. Fox example,
struct A
{
     char a;
     long b;
};
struct B
{
     short c;
     struct A d;
}
Then for struct A, a would be aligned with the size of 1 byte (sizeof(char)). Since the alignment size of b is 4 bytes (sizeof(long int)), three bytes need to be padded after a in order to guarantee b aligned in one address of multiple of 4. The final size of struct A is 8 which is multiple of 4. Therefore the alignment of struct A is 4. (Think about what if the positions of a and b switch in struct A.) About struct B, it contains one compound of struct A. So first the alignment of this compound should be considered. It is 4 as explained before. Two bytes are padded after c and the final size of struct B is 12 which satisfies the requirement.
Note: 1), The final size of struct is not equal to (N x Len), where N is the number of data members and Len is the maximal size of data members. The objective is to save memory allocation as much as possible. Look at this example:
struct C
{
char x1;
short x2;
int x3;
char x4;
};
The size of struct C is not 16.
2), For arrays, the alignment size is the size of data type but not the size of the array. For example,
long int c[20];
The alignment size for c is sizeof(long) but not sizeof(c).
- Alignment with #pragma
Force structs to align n bytes: #pragma pack(n)
Cancel alignment of n bytes: #pragma pack()
The alignment size of each data member should be the minimal value between its natural alignment size and n.
In summary, 1) defining the alignment size for each data member (compounds first), 2) align data members in sequence, 3) save memory as much as possible.
- Offset Calculation
(size_t)((char *)&((struct A *)0)->f - (char *)((struct A *)0))
* Initialization of struct
- struct A a = {'t', 'c', 8, 0.99, "example"}; or
struct A a = {0}; /* Every member is 0 now, no matter which type.*/
* Assignment of struct
struct A
{
     char *p;
     char c;
} a, b;
char cc = 'c';
a.p = &cc;
a.c = 15;
b = a;
*b.p = 30;      /* cc now is changed */
If the pointer is contained in struct, when assignment happens between two variables, two pointers are point to the same memory.
Although arrays could not be assigned to each other, they could if they are within one struct, like this:
struct A
{
     char array[10];
} a, b;
for (int i = 0; i < 10; ++i)
     a.array[i] = i;
b = a;     /* b.array now is the same with a.array */
* Struct For Bit Map
Under some circumstances, struct could be used to do bit map for a block of memory, like this
struct A
{
     int a:1;
     int b:7;
} t;
t.a = 1;
t.b = 0x7f;
- The total number of bits should be reasonable. It might be the size of one of basic data types, like char, short, int, long int, etc.
- Be cautious that t.a and t.b are defined as SIGNED int. Therefore, one bit needs to be the sign and the value ranges of them are [0,-1] and [-64, 63]. Unsigned data type might be more useful for this kind of struct usage since each bit in this struct should be meaningful.
- Almost everything of bit field in struct is implementation-dependent. Make sure everything, like which end starts in bit order, whether it allows cross the boundary of byte, etc. before use this data structure.
* Union in Memory
In general, all data members of one union start at the same low memory address. This property would be used to exploit some special usages of union.
union bits32
{
     char bytes[4];
     int whole;
} t;
t.whole = 0x12345678;
t.bytes[0] = 0x90; => Now t.whole becomes 0x12345690 in little endian system.
Another classic example of union to check system endian:
t.whole = 1;
return (t.bytes[0] == 1); /* True is little endian and false is big endian */
Or:
union bits32 endian_test = { { 'l', '?', '?', 'b' } };
#define ENDIANNESS ((char)endian_test.whole)
Unsigned and Signed Integer
* Two attributes for char, short, int, long and long long:
bitwidth (8, 16, 32, 64 bits) and sign (unsigned, signed).
* Conversion Rule
- When an expression does operations with the same bitwidth on (signed/unsigned)char, (signed/unsigned)short, bit-field, enum, these types would be promoted to int type. And float type would be promoted to double type. This is called type promotion.
- When an expression contains variables or numbers whose bitwidths are different, all variables or numbers would be converted to the wider data type (signed or unsigned) and continue the operation. This is called universal arithmetic conversions. The conversion rule is to extend the sign to the bytes of high addresses since in general the allocation of memory is from low address to high address in stack and heap. On the other hand, the opposite conversion from wider bitwidth to narrower bitwidth, the bytes with high addresses, which contains the sign, would be discarded. Keep in mind different results due to the big and little endian of the system.
* Arithmetic Operation of Unsigned
- When an expression contains variables or numbers that are with the SAME bitwidth but different sign, the signed data is converted to the unsigned version. This might bring some trouble when it happens in the condition check, like this:
unsigned int a = 6;
int b = -20;
int c = (a+b>6) ? a : b;
The c would be always equal to a.
- The general arithmetic operations on unsigned:
c = a +/- b mod 2^n
where n is the bitwidth of the data type. Therefore no overflow and underflow for unsigned data. This might be not expected in some cases.
If both operands are signed, the result of overflow/underflow is UNDEFINED. In general, it is hard to test overflow/underflow of SIGNED integer operations. It could be done to check the flags of some status register in Assembly. However if x and y are two integers and known to be non-negative, it could be done in this way:
if ((int)((unsigned)x + (unsigned)y) < 0)
    complain();
* Shift Operations
- If the item is left shifted, zeros are padded in the right. Not left shift signed data.
- "If the item being right shifted is unsigned, zeroes are shifted in. If the item is signed, the implementation is permitted to fill vacated bit positions either with zeroes or with copies of the sign bit. If you care about vacated bits in a right shift, declare the variable in question as unsigned. You are then entitled to assume that vacated bits will be set to zero."
- "if the item being right or left shifted is n bits long, then the shift count must be greater than or equal to zero and strictly less than n. Thus, it is not possible to shift all the bits out of a value in a single operation."
- By shifting bits, the multiplication and division for unsigned and multiplication for signed are safe and correct. "Note that a right shift of a signed integer is generally not equivalent to division by a power of two, even if the implementation copies the sign into vacated bits. To prove this, consider that the value of (-1)>>1 cannot possibly be zero."
* size_t
- size_t = unsigned long int
- The return data type of sizeof is size_t. Keep in mind the rules of conversion and unsigned arithmetic operations. For example,
#define TOTAL (sizeof(array)/sizeof(array[0]))
{
     int d = -1;
     if (d <= TOTAL-2)
         x = array[d+1];
}
* Post-fix UL and L
If an expression has the overflowed value, consider to put these post-fix on integer numbers: U, L, and UL. Fox example, write a routine to calculate n! assuming the result would not make long int overflow.
long foo(int n)
{
     return ((n+1L) * n / 2);
}
* Usage of Unsigned Data in C
- Unsigned version is ONLY used in BIT OPERATIONS (&, |, ~, >>, <<). Otherwise, CAST it to signed version.
* Identify Whether a Data Type or Variable Is Unsigned
- For variables: #define ISUNSIGNED(a) ((a) >= (char)0 && ~(a) >= (char)0)
- For data type: #define ISUNSIGNED(type) ((type)0 - (char)1 > (char)0)
bitwidth (8, 16, 32, 64 bits) and sign (unsigned, signed).
* Conversion Rule
- When an expression does operations with the same bitwidth on (signed/unsigned)char, (signed/unsigned)short, bit-field, enum, these types would be promoted to int type. And float type would be promoted to double type. This is called type promotion.
- When an expression contains variables or numbers whose bitwidths are different, all variables or numbers would be converted to the wider data type (signed or unsigned) and continue the operation. This is called universal arithmetic conversions. The conversion rule is to extend the sign to the bytes of high addresses since in general the allocation of memory is from low address to high address in stack and heap. On the other hand, the opposite conversion from wider bitwidth to narrower bitwidth, the bytes with high addresses, which contains the sign, would be discarded. Keep in mind different results due to the big and little endian of the system.
* Arithmetic Operation of Unsigned
- When an expression contains variables or numbers that are with the SAME bitwidth but different sign, the signed data is converted to the unsigned version. This might bring some trouble when it happens in the condition check, like this:
unsigned int a = 6;
int b = -20;
int c = (a+b>6) ? a : b;
The c would be always equal to a.
- The general arithmetic operations on unsigned:
c = a +/- b mod 2^n
where n is the bitwidth of the data type. Therefore no overflow and underflow for unsigned data. This might be not expected in some cases.
If both operands are signed, the result of overflow/underflow is UNDEFINED. In general, it is hard to test overflow/underflow of SIGNED integer operations. It could be done to check the flags of some status register in Assembly. However if x and y are two integers and known to be non-negative, it could be done in this way:
if ((int)((unsigned)x + (unsigned)y) < 0)
    complain();
* Shift Operations
- If the item is left shifted, zeros are padded in the right. Not left shift signed data.
- "If the item being right shifted is unsigned, zeroes are shifted in. If the item is signed, the implementation is permitted to fill vacated bit positions either with zeroes or with copies of the sign bit. If you care about vacated bits in a right shift, declare the variable in question as unsigned. You are then entitled to assume that vacated bits will be set to zero."
- "if the item being right or left shifted is n bits long, then the shift count must be greater than or equal to zero and strictly less than n. Thus, it is not possible to shift all the bits out of a value in a single operation."
- By shifting bits, the multiplication and division for unsigned and multiplication for signed are safe and correct. "Note that a right shift of a signed integer is generally not equivalent to division by a power of two, even if the implementation copies the sign into vacated bits. To prove this, consider that the value of (-1)>>1 cannot possibly be zero."
* size_t
- size_t = unsigned long int
- The return data type of sizeof is size_t. Keep in mind the rules of conversion and unsigned arithmetic operations. For example,
#define TOTAL (sizeof(array)/sizeof(array[0]))
{
     int d = -1;
     if (d <= TOTAL-2)
         x = array[d+1];
}
* Post-fix UL and L
If an expression has the overflowed value, consider to put these post-fix on integer numbers: U, L, and UL. Fox example, write a routine to calculate n! assuming the result would not make long int overflow.
long foo(int n)
{
     return ((n+1L) * n / 2);
}
* Usage of Unsigned Data in C
- Unsigned version is ONLY used in BIT OPERATIONS (&, |, ~, >>, <<). Otherwise, CAST it to signed version.
* Identify Whether a Data Type or Variable Is Unsigned
- For variables: #define ISUNSIGNED(a) ((a) >= (char)0 && ~(a) >= (char)0)
- For data type: #define ISUNSIGNED(type) ((type)0 - (char)1 > (char)0)
Subscribe to:
Posts (Atom)
