Details of C that every not so noob forgets
June 15, 2017
I already wrote a post about Details of C that every noob forgets. In this post I will write about more advanced details. I hope this will improve your understand of the obscure C. By the way, for the a complete review I recommend to read Expert C Programming: Deep C Secrets by Peter van der Linden or my notes.
WTF? const
is not constant
The keyword const
doesn’t mean constant expression , it only gives the status of read-only.
const int two=2;
switch (i) {
/*case const-expr: statements*/
case 1: printf("case 1 \n");
case two: printf("case 2 \n");
**error** ^^^ integral constant expression expected
case 3: printf("case 3 \n");
default: ; }
Typedef vs macros
Even though they might look the same, these guys are totally different.
#define type1 int
typedef int type2;
int main(){
type1 a; // works
type2 b; // works
unsigned type1 c; // works
unsigned type2 d; // illegal
}
typedef
defines a new data type, while #define
is just a replacement.
For example typedef int type2;
means that type2
is int, whereas #define type1 int
means that type1
will be replaced by int
every time it appears in the code.
Thus, you can even to the following:
#define type1 int
typedef type1 type2;
Again #define
does not define a new data type.
#define int_ptr int *
int_ptr a,b;
/*
this means:
int *a, b;
which is different from
int *a, *b;
*/
Do not self-deSTRUCT
I used to make this mistake when I was learning C. Be careful when using typedef
with struct
.
// struct with foo as a tag
struct foo{
int a;
char b;
};
// foo1 is a struct foo
typedef struct foo{
int a;
char b;
} foo1;
Check the following statements:
foo a; // illegal
// there is not 'foo' type
struct foo a; // good
foo1 a; // good
struct foo1 a; // illegal
// foo1 is already a data type
EPIC BATTLE: arrays vs pointers
Accessing data from array is different if we use pointers.
char a[5] = "abcd";
c = a[2];
/*
in symtab: a is on 0x4000
a = | a | b | c | d | '\0' |
*/
If we want to access the third element a[2]
- 0x4000 + 2 , directly!!
- get value from 0x4002
0x0000000000400509 main+19 movzbl -0x1e(%rbp),%eax
0x000000000040050d main+23 mov %al,-0x1(%rbp)
In case of pointers:
char *p = a;
c = *(p+2);
To access the third element:
- get address of p
- p+2
- get value from that address
0x000000000040052d main+55 mov -0x10(%rbp),%rax
0x0000000000400531 main+59 movzbl 0x2(%rax),%eax
0x0000000000400535 main+63 mov %al,-0x1(%rbp)
The array definition will allocate N bytes, whereas a pointer definition will only allocate a word size.
int m[3];
/* sizeof(m) = 12 */
int *p;
/* sizeof(p) = 8 (CPU of 64 bits)*/
Another example:
char a[] = "hallo";
char *b = "hallo";
a[]
is an array that holds {'h','a','l','l','o','\0'}
, and it can be modified.
*b
is a pointer to a read-only section that holds "hallo"
.
One last example:
int main(){
// an array
char a[] = "hola";
// a pointer
char *b;
// a pointer to read-only memory
char *c = "hallo";
// b can point to a or c
b = c;
b = a;
// c can point to a or c
c = a;
c = b;
// but a cannot point to a or c,
// because it is not a FUCKING POINTER!!!!!
a = b;
// error: assignment to expression with array type a = b;
}
When an array is a pointer
Declarations themselves can be further split into three cases:
- declaration of an external array
extern char a[];
.a
cannot be rewritten as a pointer. - definition of an array (remember, a definition is just a special case of a declaration; it
allocates space and may provide an initial value).
char a[10];
.a
cannot be rewritten as a pointer. - declaration of a function parameter
int function ( char a[]);
.a
is rewritten as a pointer whether you like it or not.
void my_function_1( int a [2][3][5] ) { ; }
void my_function_2( int a [][3][5] ) { ; }
void my_function_3( int (*a)[3][5] ) { ; }
int main(){
int arr[2][3][5];
my_function_1( arr );
my_function_2( arr );
my_function_3( arr );
int (*p) [3][5] = arr;
my_function_1( p );
my_function_2( p );
my_function_3( p );
int (*q)[2][3][5] = &arr;
my_function_1( *q );
my_function_2( *q );
my_function_3( *q );
}
In summary, the rule array name is rewritten as a pointer argument isn’t recursive. An array of arrays is rewritten as a pointer to arrays NOT as a pointer to pointer.
Thus,
- array of array
char a[9][4]
can be rewritten aschar (*a)[4]
pointer to array - array of pointers
char *a[4]
can be rewritten aschar **a
pointer to pointer - pointer to array
char (*a)[4]
ischar (*a)[4]
pointer to array - pointer to pointer
char **a
ischar **a
pointer to pointer
The very common case is the main
function:
int main(int argc, char **argv){}
int main(int argc, char *argv[]){}
Just for fun
Read the following one-liner:
main() { printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);}`
David Korn wrote this one-liner and won the iocc contest in 1987
Try to understand yourself the code, there are some extra info you might need:
unix
is a predefined macro with value 1#define unix 1
a[i]
is resolved by the compiler as*(a+i)
a[i][j]
is resolved by the compiler as*( *(a+i) + j)
Last words
C is a very simple language if you compare to python or even c++, and still very powerful. I hope you enjoy all the information provided here. Let me know if you have some questions.
Have fun!
Share it!
Comments powered by Talkyard.