The document discusses how programming in C is difficult due to the need for a deep understanding of the language. It describes how even professional programmers often do not fully understand C and can write undefined or unspecified code as a result. The talk will examine small code snippets in C to discuss fundamental concepts, limitations, and design philosophies of the language.
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0 ratings0% found this document useful (0 votes)
33K views232 pages
DeepC Scandev Mar2013
The document discusses how programming in C is difficult due to the need for a deep understanding of the language. It describes how even professional programmers often do not fully understand C and can write undefined or unspecified code as a result. The talk will examine small code snippets in C to discuss fundamental concepts, limitations, and design philosophies of the language.
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 232
Programming is hard. Programming correct C is particularly hard.
Indeed, it is uncommon to see a
screenful containing only well dened and conforming code. Why do professional programmers write code like this? Because most programmers do not have a deep understanding of the language they are using. While they sometimes know that certain things are undened or unspecied, they often do not know why it is so. In this talk we will study small code snippets in C, and use them to discuss some of the fundamental building blocks, limitations and underlying design philosophies of this wonderful but dangerous programming language. Deep C http://www.noaanews.noaa.gov/stories2005/images/rov-hercules-titanic.jpg by Olve Maudal A 50 minute session at Scandinavian Developer Conference 2013 Tuesday, March 5, 2013 Exercise #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c What do you think this code snippet might print if you compile, link and run it in your development environment? Exercise #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c What do you think this code snippet might print if you compile, link and run it in your development environment? On my computer (Mac OS 10.8.2, gcc 4.2.1, clang 4.1, icc 13.0.1): Exercise #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c What do you think this code snippet might print if you compile, link and run it in your development environment? On my computer (Mac OS 10.8.2, gcc 4.2.1, clang 4.1, icc 13.0.1): $ gcc foo.c && ./a.out Exercise #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c What do you think this code snippet might print if you compile, link and run it in your development environment? On my computer (Mac OS 10.8.2, gcc 4.2.1, clang 4.1, icc 13.0.1): $ gcc foo.c && ./a.out 12 Exercise #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c What do you think this code snippet might print if you compile, link and run it in your development environment? On my computer (Mac OS 10.8.2, gcc 4.2.1, clang 4.1, icc 13.0.1): $ gcc foo.c && ./a.out 12 $ clang foo.c && ./a.out Exercise #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c What do you think this code snippet might print if you compile, link and run it in your development environment? On my computer (Mac OS 10.8.2, gcc 4.2.1, clang 4.1, icc 13.0.1): $ gcc foo.c && ./a.out 12 $ clang foo.c && ./a.out 11 Exercise #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c What do you think this code snippet might print if you compile, link and run it in your development environment? On my computer (Mac OS 10.8.2, gcc 4.2.1, clang 4.1, icc 13.0.1): $ gcc foo.c && ./a.out 12 $ clang foo.c && ./a.out 11 $ icc foo.c && ./a.out Exercise #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c What do you think this code snippet might print if you compile, link and run it in your development environment? On my computer (Mac OS 10.8.2, gcc 4.2.1, clang 4.1, icc 13.0.1): $ gcc foo.c && ./a.out 12 $ clang foo.c && ./a.out 11 $ icc foo.c && ./a.out 13 #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c Lets add some ags for better diagnostics. #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c $ gcc -std=c99 -O -Wall -Wextra -pedantic foo.c && ./a.out 12 $ clang -std=c99 -O -Wall -Wextra -pedantic foo.c && ./a.out 11 $ icc -std=c99 -O -Wall -Wextra -pedantic foo.c && ./a.out 13 On my computer (Mac OS 10.8.2, gcc 4.2.1, clang 4.1, icc 13.0.1): Lets add some ags for better diagnostics. It is important to understand that C (and C++) are not really high-level languages compared to most other common programming languages. They are more like just portable assemblers where you have to appreciate the underlying architecture to program correctly. This is reected in the language denition and in how compiler deals with incorrect code. Without a deep understanding of the language, its history, and its design goals, you are doomed to fail. http://www.slideshare.net/olvemaudal/deep-c #include <stdio.h> void foo(void) { int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } #include <stdio.h> void foo(void) { int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 4 #include <stdio.h> void foo(void) { int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 4 4 #include <stdio.h> void foo(void) { int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 4 4 4 #include <stdio.h> void foo(void) { int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 4 4 4 #include <stdio.h> void foo(void) { static int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } #include <stdio.h> void foo(void) { static int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 4 #include <stdio.h> void foo(void) { static int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 4 5 #include <stdio.h> void foo(void) { static int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 4 5 6 #include <stdio.h> void foo(void) { static int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } garbage, garbage, garbage? No. Variables with static storage duration are initialized to 0 #include <stdio.h> void foo(void) { static int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } garbage, garbage, garbage? No. Variables with static storage duration are initialized to 0 It is better to initialize explicitly. #include <stdio.h> void foo(void) { static int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } garbage, garbage, garbage? No. Variables with static storage duration are initialized to 0 It is better to initialize explicitly. I agree, in this case. But, as a professional programmer, you sometimes have to read code written by other people. #include <stdio.h> void foo(void) { static int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 garbage, garbage, garbage? No. Variables with static storage duration are initialized to 0 It is better to initialize explicitly. I agree, in this case. But, as a professional programmer, you sometimes have to read code written by other people. #include <stdio.h> void foo(void) { static int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 2 garbage, garbage, garbage? No. Variables with static storage duration are initialized to 0 It is better to initialize explicitly. I agree, in this case. But, as a professional programmer, you sometimes have to read code written by other people. #include <stdio.h> void foo(void) { static int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 2 3 garbage, garbage, garbage? No. Variables with static storage duration are initialized to 0 It is better to initialize explicitly. I agree, in this case. But, as a professional programmer, you sometimes have to read code written by other people. #include <stdio.h> void foo(void) { static int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } #include <stdio.h> void foo(void) { static int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } Yes, in theory that is correct. 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? In C. Why do you think static variables gets a default value (usually 0), while auto variables does not get a default value? In C. Why do you think static variables gets a default value (usually 0), while auto variables does not get a default value? Because C is a braindead programming language? In C. Why do you think static variables gets a default value (usually 0), while auto variables does not get a default value? Because C is a braindead programming language? Because C is all about execution speed. Setting static variables to default values is a one time cost, while defaulting auto variables might add a signcant runtime cost. #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } Yes, in theory that is correct. Lets try it on my machine 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 Yes, in theory that is correct. Lets try it on my machine 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 2 Yes, in theory that is correct. Lets try it on my machine 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 2 3 Yes, in theory that is correct. Lets try it on my machine 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 2 3 Yes, in theory that is correct. Lets try it on my machine Ehh... 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 2 3 Yes, in theory that is correct. Lets try it on my machine any plausible explaination for this behaviour? Ehh... 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 2 3 Yes, in theory that is correct. Lets try it on my machine any plausible explaination for this behaviour? Ehh... Is it because: The value of an object with automatic storage duration is used while it is indeterminate? 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 2 3 Yes, in theory that is correct. Lets try it on my machine any plausible explaination for this behaviour? Ehh... Is it because: The value of an object with automatic storage duration is used while it is indeterminate? That explains why this is undened behavior, but it does not explain the phenomenon we just observed: 1,2,3 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 2 3 Yes, in theory that is correct. Lets try it on my machine any plausible explaination for this behaviour? Ehh... Is it because: The value of an object with automatic storage duration is used while it is indeterminate? That explains why this is undened behavior, but it does not explain the phenomenon we just observed: 1,2,3 But if it is UB, do I need to care? 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } 1 2 3 Yes, in theory that is correct. Lets try it on my machine any plausible explaination for this behaviour? Ehh... Is it because: The value of an object with automatic storage duration is used while it is indeterminate? That explains why this is undened behavior, but it does not explain the phenomenon we just observed: 1,2,3 But if it is UB, do I need to care? You do, because everything becomes much easier if you can and are willing to reason about these things... 1, 1, 1? No, variables with automatic storage duration are not initialized implicitly Garbage, garbage, garbage? #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } But seriously, I dont need to know, because I let the compiler nd bugs like this #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } OK, lets add some ags But seriously, I dont need to know, because I let the compiler nd bugs like this #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -Wall -Wextra -pedantic foo.c OK, lets add some ags But seriously, I dont need to know, because I let the compiler nd bugs like this #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -Wall -Wextra -pedantic foo.c $ ./a.out OK, lets add some ags But seriously, I dont need to know, because I let the compiler nd bugs like this #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -Wall -Wextra -pedantic foo.c $ ./a.out 1 OK, lets add some ags But seriously, I dont need to know, because I let the compiler nd bugs like this #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -Wall -Wextra -pedantic foo.c $ ./a.out 1 2 OK, lets add some ags But seriously, I dont need to know, because I let the compiler nd bugs like this #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -Wall -Wextra -pedantic foo.c $ ./a.out 1 2 3 OK, lets add some ags But seriously, I dont need to know, because I let the compiler nd bugs like this #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -Wall -Wextra -pedantic foo.c $ ./a.out 1 2 3 OK, lets add some ags Lousy compiler! But seriously, I dont need to know, because I let the compiler nd bugs like this Why dont the C standard require that you always get a warning or error on invalid code? Why dont the C standard require that you always get a warning or error on invalid code? Because C is a braindead programming language? Why dont the C standard require that you always get a warning or error on invalid code? Because C is a braindead programming language? One of the design goals of C is that it should be relatively easy to write a compiler. Adding a requirement that the compilers should refuse or warn about invalid code would add a huge burden on the compiler writers. #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } Pro tip: Always compile with optimization! #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } Pro tip: Always compile with optimization! #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -O -Wall -Wextra foo.c Pro tip: Always compile with optimization! #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function Pro tip: Always compile with optimization! #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function 1494497536 Pro tip: Always compile with optimization! #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function 1494497536 1494495224 Pro tip: Always compile with optimization! #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function 1494497536 1494495224 1494495224 Pro tip: Always compile with optimization! #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function 1494497536 1494495224 1494495224 $ cc -O -Wall -Wextra foo.c Pro tip: Always compile with optimization! #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function 1494497536 1494495224 1494495224 $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function Pro tip: Always compile with optimization! #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function 1494497536 1494495224 1494495224 $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function 1450342656 Pro tip: Always compile with optimization! #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function 1494497536 1494495224 1494495224 $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function 1450342656 1450340344 Pro tip: Always compile with optimization! #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function 1494497536 1494495224 1494495224 $ cc -O -Wall -Wextra foo.c foo.c:6: warning: 'a' is used uninitialized in this function 1450342656 1450340344 1450340344 I am now going to show you something cool! #include <stdio.h> void foo(void) { int a; printf("%d\n", a); } void bar(void) { int a = 42; }
int main(void) { bar(); foo(); } I am now going to show you something cool! #include <stdio.h> void foo(void) { int a; printf("%d\n", a); } void bar(void) { int a = 42; }
int main(void) { bar(); foo(); } $ cc foo.c && ./a.out I am now going to show you something cool! #include <stdio.h> void foo(void) { int a; printf("%d\n", a); } void bar(void) { int a = 42; }
int main(void) { bar(); foo(); } $ cc foo.c && ./a.out 42 I am now going to show you something cool! #include <stdio.h> void foo(void) { int a; printf("%d\n", a); } void bar(void) { int a = 42; }
int main(void) { bar(); foo(); } $ cc foo.c && ./a.out 42 I am now going to show you something cool! Can you explain this behavior? #include <stdio.h> void foo(void) { int a; printf("%d\n", a); } void bar(void) { int a = 42; }
int main(void) { bar(); foo(); } $ cc foo.c && ./a.out 42 I am now going to show you something cool! Can you explain this behavior? If you can give a plausible explanation for this behavior, you should feel both good and bad. Bad because you obviously know something you are supposed to not know when programming in C. You make assumptions about the underlying implementation and architecture. Good because being able to understand such phenomenons are essential for troubleshooting C programs and for avoiding falling into all the traps laid out for you. #include <stdio.h> void foo(void) { int a; printf("%d\n", a); } void bar(void) { int a = 42; }
int main(void) { bar(); foo(); } $ cc foo.c && ./a.out 42 I am now going to show you something cool! Can you explain this behavior? #include <stdio.h> void foo(void) { int a; printf("%d\n", a); } void bar(void) { int a = 42; }
int main(void) { bar(); foo(); } eh? $ cc foo.c && ./a.out 42 I am now going to show you something cool! Can you explain this behavior? #include <stdio.h> void foo(void) { int a; printf("%d\n", a); } void bar(void) { int a = 42; }
int main(void) { bar(); foo(); } eh? $ cc foo.c && ./a.out 42 I am now going to show you something cool! Perhaps this compiler has a pool of named variables that it reuses. Eg variable a was used and released in bar(), then when foo() needs an integer named a it will get the same variable for reuse. If you rename the variable in bar() to, say b, then I dont think you will get 42. Can you explain this behavior? #include <stdio.h> void foo(void) { int a; printf("%d\n", a); } void bar(void) { int a = 42; }
int main(void) { bar(); foo(); } eh? $ cc foo.c && ./a.out 42 I am now going to show you something cool! Perhaps this compiler has a pool of named variables that it reuses. Eg variable a was used and released in bar(), then when foo() needs an integer named a it will get the same variable for reuse. If you rename the variable in bar() to, say b, then I dont think you will get 42. Yeah, sure... Can you explain this behavior? Strange explanations are often symptoms of having an invalid conceptual model! Text Segment Data Segment Execution Stack Heap high address low address Memory Layout * (automatic storage) (allocated storage) (static storage) (instructions / read only data) And sometimes it is useful to assume that an activation record is created and pushed onto the execution stack every time a function is called. The activation record contains local auto variables, arguments to the functions, and housekeeping data such as pointer to the previous frame and the return address. Activation Record housekeeping data arguments local auto variables (*) The C standard does not dictate any particular memory layout, so what is presented here is just a useful conceptual example model that is similar to what some architecture and run-time enviornments look like It is sometimes useful to assume that a C program uses a memory model where the instructions are stored in a text segment, and static variables are stored in a data segment. Automatic variables are allocated when needed together with housekeeping variables on an execution stack that is growing towards low address. The remaining memory, the heap is used for allocated storage. The stack and the heap is typically not cleaned up in any way at startup, or during execution, so before objects are explicitly initialized they typically get garbage values based on whatever is left in memory from discarded objects and previous executions. In other words, the programmer must do all the housekeeping on variables with automatic storage and allocated storage. #include <stdio.h> int foo(int a) { printf("%d", a); return a; } int bar(int a, int b) { return a + b; }
int main(void) { int i = foo(3) + foo(4); printf("%d\n", i);
int j = bar(foo(3), foo(4)); printf("%d\n", j); } #include <stdio.h> int foo(int a) { printf("%d", a); return a; } int bar(int a, int b) { return a + b; }
int main(void) { int i = foo(3) + foo(4); printf("%d\n", i);
int j = bar(foo(3), foo(4)); printf("%d\n", j); } $ cc foo.c && ./a.out #include <stdio.h> int foo(int a) { printf("%d", a); return a; } int bar(int a, int b) { return a + b; }
int main(void) { int i = foo(3) + foo(4); printf("%d\n", i);
int j = bar(foo(3), foo(4)); printf("%d\n", j); } $ cc foo.c && ./a.out 347 #include <stdio.h> int foo(int a) { printf("%d", a); return a; } int bar(int a, int b) { return a + b; }
int main(void) { int i = foo(3) + foo(4); printf("%d\n", i);
int j = bar(foo(3), foo(4)); printf("%d\n", j); } $ cc foo.c && ./a.out 347 437 #include <stdio.h> int foo(int a) { printf("%d", a); return a; } int bar(int a, int b) { return a + b; }
int main(void) { int i = foo(3) + foo(4); printf("%d\n", i);
int j = bar(foo(3), foo(4)); printf("%d\n", j); } $ cc foo.c && ./a.out 347 437 but you might also get #include <stdio.h> int foo(int a) { printf("%d", a); return a; } int bar(int a, int b) { return a + b; }
int main(void) { int i = foo(3) + foo(4); printf("%d\n", i);
int j = bar(foo(3), foo(4)); printf("%d\n", j); } $ cc foo.c && ./a.out 347 437 437 347 but you might also get #include <stdio.h> int foo(int a) { printf("%d", a); return a; } int bar(int a, int b) { return a + b; }
int main(void) { int i = foo(3) + foo(4); printf("%d\n", i);
int j = bar(foo(3), foo(4)); printf("%d\n", j); } $ cc foo.c && ./a.out 347 437 437 347 but you might also get or #include <stdio.h> int foo(int a) { printf("%d", a); return a; } int bar(int a, int b) { return a + b; }
int main(void) { int i = foo(3) + foo(4); printf("%d\n", i);
int j = bar(foo(3), foo(4)); printf("%d\n", j); } $ cc foo.c && ./a.out 347 437 437 347 but you might also get 437 437 or #include <stdio.h> int foo(int a) { printf("%d", a); return a; } int bar(int a, int b) { return a + b; }
int main(void) { int i = foo(3) + foo(4); printf("%d\n", i);
int j = bar(foo(3), foo(4)); printf("%d\n", j); } $ cc foo.c && ./a.out 347 437 437 347 but you might also get 437 437 or or #include <stdio.h> int foo(int a) { printf("%d", a); return a; } int bar(int a, int b) { return a + b; }
int main(void) { int i = foo(3) + foo(4); printf("%d\n", i);
int j = bar(foo(3), foo(4)); printf("%d\n", j); } $ cc foo.c && ./a.out 347 437 437 347 but you might also get 437 437 347 347 or or #include <stdio.h> int foo(int a) { printf("%d", a); return a; } int bar(int a, int b) { return a + b; }
int main(void) { int i = foo(3) + foo(4); printf("%d\n", i);
int j = bar(foo(3), foo(4)); printf("%d\n", j); } $ cc foo.c && ./a.out 347 437 437 347 C and C++ are among the few programming languages where evaluation order is mostly unspecied. This is an example of unspecied behaviour. but you might also get 437 437 347 347 or or In C. Why is the evaluation order mostly unspecied? In C. Why is the evaluation order mostly unspecied? In C. Why is the evaluation order mostly unspecied? Because C is a braindead programming language? In C. Why is the evaluation order mostly unspecied? Because C is a braindead programming language? Because there is a design goal to allow optimal execution speed on a wide range of architectures. In C the compiler can choose to evaluate expressions in the order that is most optimal for a particular platform. This allows for great optimization opportunities. #include <stdio.h> int main(void) { int v[6] = {4,6,2,9}; int i = 2; int j = i * 3 + v[i++]; printf(%d\n, j); } #include <stdio.h> int main(void) { int v[6] = {4,6,2,9}; int i = 2; int j = i * 3 + v[i++]; printf(%d\n, j); } $ cc foo.cpp && ./a.out #include <stdio.h> int main(void) { int v[6] = {4,6,2,9}; int i = 2; int j = i * 3 + v[i++]; printf(%d\n, j); } $ cc foo.cpp && ./a.out 42 #include <stdio.h> int main(void) { int v[6] = {4,6,2,9}; int i = 2; int j = i * 3 + v[i++]; printf(%d\n, j); } $ cc foo.cpp && ./a.out 42 What? Inconceivable! #include <stdio.h> int main(void) { int v[6] = {4,6,2,9}; int i = 2; int j = i * 3 + v[i++]; printf(%d\n, j); } $ cc foo.cpp && ./a.out 42 What? Inconceivable! This is a classic example of undened behaviour. Anything can happen! Nasal demons can start ying out of your nose! #include <stdio.h> int main(void) { int v[6] = {4,6,2,9}; int i = 2; int j = i * 3 + v[i++]; printf(%d\n, j); } $ cc foo.cpp && ./a.out 42 What? Inconceivable! This is a classic example of undened behaviour. Anything can happen! Nasal demons can start ying out of your nose! I agree this is crap code, but why is it wrong? #include <stdio.h> int main(void) { int v[6] = {4,6,2,9}; int i = 2; int j = i * 3 + v[i++]; printf(%d\n, j); } What? Inconceivable! $ cc foo.cpp && ./a.out 42 This is a classic example of undened behaviour. Anything can happen! Nasal demons can start ying out of your nose! I agree this is crap code, but why is it wrong? In this case? Line 6. What is i*3? Is it 2*3 or 3*3 or something else? In C you can not assume anything about a variable with side-effects (here i++) before there is a sequence point. $ cc foo.cpp && ./a.out 42 I dont care, I never write code like that. #include <stdio.h> int main(void) { int v[6] = {4,6,2,9}; int i = 2; int j = i * 3 + v[i++]; printf(%d\n, j); } $ cc foo.cpp && ./a.out 42 Good for you. But bugs like this can easily happen if you dont understand the rules of sequencing. And very often, the compiler is not able to help you... I dont care, I never write code like that. #include <stdio.h> int main(void) { int v[6] = {4,6,2,9}; int i = 2; int j = i * 3 + v[i++]; printf(%d\n, j); } $ cc foo.cpp && ./a.out 42 Good for you. But bugs like this can easily happen if you dont understand the rules of sequencing. And very often, the compiler is not able to help you... I dont care, I never write code like that. But why do we not get warning on this by default? #include <stdio.h> int main(void) { int v[6] = {4,6,2,9}; int i = 2; int j = i * 3 + v[i++]; printf(%d\n, j); } $ cc foo.cpp && ./a.out 42 Good for you. But bugs like this can easily happen if you dont understand the rules of sequencing. And very often, the compiler is not able to help you... I dont care, I never write code like that. But why do we not get warning on this by default? At least two reasons. First of all it is sometimes very difcult to detect such sequencing violations. Secondly, there is so much existing code out there that breaks these rules, so issuing warnings here might cause other problems. #include <stdio.h> int main(void) { int v[6] = {4,6,2,9}; int i = 2; int j = i * 3 + v[i++]; printf(%d\n, j); } What do these code snippets print? What do these code snippets print? int a=41; a++; printf("%d\n", a); 1 What do these code snippets print? int a=41; a++ & printf("%d\n", a); 2 int a=41; a++; printf("%d\n", a); 1 What do these code snippets print? int a=41; a++ && printf("%d\n", a); 3 int a=41; a++ & printf("%d\n", a); 2 int a=41; a++; printf("%d\n", a); 1 What do these code snippets print? int a=41; a++ && printf("%d\n", a); 3 int a=41; if (a++ < 42) printf("%d\n", a); 4 int a=41; a++ & printf("%d\n", a); 2 int a=41; a++; printf("%d\n", a); 1 What do these code snippets print? int a=41; a++ && printf("%d\n", a); 3 int a=41; if (a++ < 42) printf("%d\n", a); 4 int a=41; a++ & printf("%d\n", a); 2 int a=41; a = a++; printf("%d\n", a); 5 int a=41; a++; printf("%d\n", a); 1 What do these code snippets print? int a=41; a++ && printf("%d\n", a); 3 int a=41; if (a++ < 42) printf("%d\n", a); 4 int a=41; a++ & printf("%d\n", a); 2 int a=41; a = a++; printf("%d\n", a); 5 int a=41; a++; printf("%d\n", a); 1 int a=41; a = foo(a++); printf("42\n"); 6 What do these code snippets print? int a=41; a++ && printf("%d\n", a); 3 int a=41; if (a++ < 42) printf("%d\n", a); 4 int a=41; a++ & printf("%d\n", a); 2 int a=41; a = a++; printf("%d\n", a); 5 int a=41; a++; printf("%d\n", a); 1 42 int a=41; a = foo(a++); printf("42\n"); 6 What do these code snippets print? int a=41; a++ && printf("%d\n", a); 3 int a=41; if (a++ < 42) printf("%d\n", a); 4 int a=41; a++ & printf("%d\n", a); 2 undened int a=41; a = a++; printf("%d\n", a); 5 int a=41; a++; printf("%d\n", a); 1 42 int a=41; a = foo(a++); printf("42\n"); 6 What do these code snippets print? int a=41; a++ && printf("%d\n", a); 3 int a=41; if (a++ < 42) printf("%d\n", a); 4 int a=41; a++ & printf("%d\n", a); 2 undened 42 int a=41; a = a++; printf("%d\n", a); 5 int a=41; a++; printf("%d\n", a); 1 42 int a=41; a = foo(a++); printf("42\n"); 6 What do these code snippets print? int a=41; a++ && printf("%d\n", a); 3 int a=41; if (a++ < 42) printf("%d\n", a); 4 int a=41; a++ & printf("%d\n", a); 2 undened 42 42 int a=41; a = a++; printf("%d\n", a); 5 int a=41; a++; printf("%d\n", a); 1 42 int a=41; a = foo(a++); printf("42\n"); 6 What do these code snippets print? int a=41; a++ && printf("%d\n", a); 3 int a=41; if (a++ < 42) printf("%d\n", a); 4 int a=41; a++ & printf("%d\n", a); 2 undened 42 42 undened int a=41; a = a++; printf("%d\n", a); 5 int a=41; a++; printf("%d\n", a); 1 42 int a=41; a = foo(a++); printf("42\n"); 6 What do these code snippets print? int a=41; a++ && printf("%d\n", a); 3 int a=41; if (a++ < 42) printf("%d\n", a); 4 int a=41; a++ & printf("%d\n", a); 2 undened 42 42 undened int a=41; a = a++; printf("%d\n", a); 5 int a=41; a++; printf("%d\n", a); 1 42 ? int a=41; a = foo(a++); printf("42\n"); 6 What do these code snippets print? int a=41; a++ && printf("%d\n", a); 3 int a=41; if (a++ < 42) printf("%d\n", a); 4 int a=41; a++ & printf("%d\n", a); 2 undened 42 42 undened int a=41; a = a++; printf("%d\n", a); 5 When exactly do side-effects take place in C and C++? int a=41; a++; printf("%d\n", a); 1 42 ? int a=41; a = foo(a++); printf("42\n"); 6 A sequence point is a point in the program's execution sequence where all previous side- effects shall have taken place and where all subsequent side-effects shall not have taken place Sequence Points Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Sequence Points - Rule 1 a = a++ this is undefined! Furthermore, the prior value shall be read only to determine the value to be stored. Sequence Points - Rule 2 a + a++ this is undefined!! Sequence Points A lot of developers think C has many sequence points Sequence Points The reality is that C has very few sequence points. This helps to maximize optimization opportunities for the compiler. Sequence points in C 1) At the end of a full expression there is a sequence point. a = i++; ++i; if (++i == 42) { ... } 2) In a function call, there is a sequence point after the evaluation of the arguments, but before the actual call. foo(++i) 3) The logical and (&&) and logical or (||) guarantees a left-to-right evaluation, and if the second operand is evaluated, there is a sequence point between the evaluation of the rst and second operands. if (p && *p++ == 42) { ... } 4) The comma operator (,) guarantees left-to-right evaluation and there is a sequence point between evaluating the left operand and the right operand. i = 39; a = (i++, i++, ++i); 5) For the conditional operator (?:), the rst operand is evaluated; there is a sequence point between its evaluation and the evaluation of the second or third operand (whichever is evaluated) a++ > 42 ? --a : ++a; #include <stdio.h> void foo(void) { int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } #include <stdio.h> void foo(void) { int a = 3; ++a; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } #include <stdio.h> void foo(void) { int a = 3; a++; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } #include <stdio.h> void foo(void) { int a = 3; a++; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc foo.c #include <stdio.h> void foo(void) { int a = 3; a++; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc foo.c $ ./a.out #include <stdio.h> void foo(void) { int a = 3; a++; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc foo.c $ ./a.out 4 #include <stdio.h> void foo(void) { int a = 3; a++; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc foo.c $ ./a.out 4 4 #include <stdio.h> void foo(void) { int a = 3; a++; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc foo.c $ ./a.out 4 4 4 #include <stdio.h> void foo(void) { int a = 3; a++; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc foo.c $ ./a.out 4 4 4 Believe it or not, I have met several programmers who thought this snippet would print 3,3,3. #include <stdio.h> void foo(void) { int a = 3; a++; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc foo.c $ ./a.out 4 4 4 They are all morons! Believe it or not, I have met several programmers who thought this snippet would print 3,3,3. #include <stdio.h> void foo(void) { int a = 3; a++; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc foo.c $ ./a.out 4 4 4 They are all morons! Believe it or not, I have met several programmers who thought this snippet would print 3,3,3. Did you know about sequence points? Do you have a deep understanding of when side-effects really take place in C? #include <stdio.h> void foo(void) { int a = 3; a++; printf("%d\n", a); }
int main(void) { foo(); foo(); foo(); } $ cc foo.c $ ./a.out 4 4 4 They are all morons! Believe it or not, I have met several programmers who thought this snippet would print 3,3,3. Did you know about sequence points? Do you have a deep understanding of when side-effects really take place in C? ehh... Strange explanations are often symptoms of having an invalid conceptual model! Behavior implementation-dened behavior: the construct is not incorrect; the code must compile; the compiler must document the behavior unspecied behavior: the same as implementation-dened except the behavior need not be documented undened behavior: the standard imposes no requirements ; anything at all can happen, all bets are off, nasal demons might y out of your nose. #include <stdio.h> #include <limits.h> #include <stdlib.h> int main() { // implementation-defined int i = ~0; i >>= 1; printf("%d\n", i); // unspecified printf("4") + printf("2"); printf("\n"); // undefined int k = INT_MAX; k += 1; printf("%d\n", k); } Note that many compilers will not give you any warnings when compiling this code, and due to the undened behavior caused by signed integer overow above, the whole program is in theory undened. Behavior implementation-dened behavior: the construct is not incorrect; the code must compile; the compiler must document the behavior unspecied behavior: the same as implementation-dened except the behavior need not be documented undened behavior: the standard imposes no requirements ; anything at all can happen, all bets are off, nasal demons might y out of your nose. #include <stdio.h> #include <limits.h> #include <stdlib.h> int main() { // implementation-defined int i = ~0; i >>= 1; printf("%d\n", i); // unspecified printf("4") + printf("2"); printf("\n"); // undefined int k = INT_MAX; k += 1; printf("%d\n", k); } Note that many compilers will not give you any warnings when compiling this code, and due to the undened behavior caused by signed integer overow above, the whole program is in theory undened. ... and, locale-specic behavior the C standard denes the expected behavior, but says very little about how it should be implemented. the C standard denes the expected behavior, but says very little about how it should be implemented. this is a key feature of C, and one of the reason why C is such a successful programming language on a wide range of hardware! int the_answer(int seed) { int answer = seed + 42; return answer - seed; } deep_thought.c int the_answer(int seed) { int answer = seed + 42; return answer - seed; } #include <stdio.h> #include <limits.h> int the_answer(int); int main(void) { printf("The answer is:\n"); int a = the_answer(INT_MAX); printf("%d\n", a); } main.c deep_thought.c int the_answer(int seed) { int answer = seed + 42; return answer - seed; } $ cc main.c deep_thought.c && ./a.out #include <stdio.h> #include <limits.h> int the_answer(int); int main(void) { printf("The answer is:\n"); int a = the_answer(INT_MAX); printf("%d\n", a); } main.c deep_thought.c int the_answer(int seed) { int answer = seed + 42; return answer - seed; } $ cc main.c deep_thought.c && ./a.out The anwser is: #include <stdio.h> #include <limits.h> int the_answer(int); int main(void) { printf("The answer is:\n"); int a = the_answer(INT_MAX); printf("%d\n", a); } main.c deep_thought.c int the_answer(int seed) { int answer = seed + 42; return answer - seed; } $ cc main.c deep_thought.c && ./a.out The anwser is: 3.1415926535897932384626433832795028841971 #include <stdio.h> #include <limits.h> int the_answer(int); int main(void) { printf("The answer is:\n"); int a = the_answer(INT_MAX); printf("%d\n", a); } main.c deep_thought.c int the_answer(int seed) { int answer = seed + 42; return answer - seed; } $ cc main.c deep_thought.c && ./a.out The anwser is: 3.1415926535897932384626433832795028841971 Inconceivable! #include <stdio.h> #include <limits.h> int the_answer(int); int main(void) { printf("The answer is:\n"); int a = the_answer(INT_MAX); printf("%d\n", a); } main.c deep_thought.c int the_answer(int seed) { int answer = seed + 42; return answer - seed; } $ cc main.c deep_thought.c && ./a.out The anwser is: 3.1415926535897932384626433832795028841971 Inconceivable! Remember... when you have undened behavior, anything can happen! You keep using that word. I do not think it means what you think it means. #include <stdio.h> #include <limits.h> int the_answer(int); int main(void) { printf("The answer is:\n"); int a = the_answer(INT_MAX); printf("%d\n", a); } main.c deep_thought.c int the_answer(int seed) { int answer = seed + 42; return answer - seed; } $ cc main.c deep_thought.c && ./a.out The anwser is: 3.1415926535897932384626433832795028841971 Inconceivable! Remember... when you have undened behavior, anything can happen! You keep using that word. I do not think it means what you think it means. #include <stdio.h> #include <limits.h> int the_answer(int); int main(void) { printf("The answer is:\n"); int a = the_answer(INT_MAX); printf("%d\n", a); } main.c deep_thought.c Integer overow gives undened behavior. If you want to prevent this to happen you must write the logic yourself. This is the spirit of C, you dont get code you have not asked for. Exercise #include <stdio.h> #include <stdbool.h> void foo(void) { bool b; if (b) printf("true\n"); if (!b) printf("false\n"); } foo.c This program is UB because b is used without being initialized. But in practice, what do you think might happen when this function is called? Exercise #include <stdio.h> #include <stdbool.h> void foo(void) { bool b; if (b) printf("true\n"); if (!b) printf("false\n"); } foo.c This program is UB because b is used without being initialized. But in practice, what do you think might happen when this function is called? void bar(void); void foo(void); int main(void) { bar(); foo(); } main.c Exercise #include <stdio.h> #include <stdbool.h> void foo(void) { bool b; if (b) printf("true\n"); if (!b) printf("false\n"); } foo.c This program is UB because b is used without being initialized. But in practice, what do you think might happen when this function is called? void bar(void); void foo(void); int main(void) { bar(); foo(); } main.c void bar(void) { char c = 2; (void)c; } bar.c Exercise #include <stdio.h> #include <stdbool.h> void foo(void) { bool b; if (b) printf("true\n"); if (!b) printf("false\n"); } foo.c This program is UB because b is used without being initialized. But in practice, what do you think might happen when this function is called? void bar(void); void foo(void); int main(void) { bar(); foo(); } main.c void bar(void) { char c = 2; (void)c; } bar.c This is what I get on my computer (Mac OS 10.8.2, gcc 4.7.2) Exercise $ gcc -v #include <stdio.h> #include <stdbool.h> void foo(void) { bool b; if (b) printf("true\n"); if (!b) printf("false\n"); } foo.c This program is UB because b is used without being initialized. But in practice, what do you think might happen when this function is called? void bar(void); void foo(void); int main(void) { bar(); foo(); } main.c void bar(void) { char c = 2; (void)c; } bar.c This is what I get on my computer (Mac OS 10.8.2, gcc 4.7.2) Exercise $ gcc -v gcc version 4.7.2 (GCC) #include <stdio.h> #include <stdbool.h> void foo(void) { bool b; if (b) printf("true\n"); if (!b) printf("false\n"); } foo.c This program is UB because b is used without being initialized. But in practice, what do you think might happen when this function is called? void bar(void); void foo(void); int main(void) { bar(); foo(); } main.c void bar(void) { char c = 2; (void)c; } bar.c This is what I get on my computer (Mac OS 10.8.2, gcc 4.7.2) Exercise $ gcc -v gcc version 4.7.2 (GCC) $ gcc foo.c bar.c main.c #include <stdio.h> #include <stdbool.h> void foo(void) { bool b; if (b) printf("true\n"); if (!b) printf("false\n"); } foo.c This program is UB because b is used without being initialized. But in practice, what do you think might happen when this function is called? void bar(void); void foo(void); int main(void) { bar(); foo(); } main.c void bar(void) { char c = 2; (void)c; } bar.c This is what I get on my computer (Mac OS 10.8.2, gcc 4.7.2) Exercise $ gcc -v gcc version 4.7.2 (GCC) $ gcc foo.c bar.c main.c $ ./a.out #include <stdio.h> #include <stdbool.h> void foo(void) { bool b; if (b) printf("true\n"); if (!b) printf("false\n"); } foo.c This program is UB because b is used without being initialized. But in practice, what do you think might happen when this function is called? void bar(void); void foo(void); int main(void) { bar(); foo(); } main.c void bar(void) { char c = 2; (void)c; } bar.c This is what I get on my computer (Mac OS 10.8.2, gcc 4.7.2) Exercise $ gcc -v gcc version 4.7.2 (GCC) $ gcc foo.c bar.c main.c $ ./a.out true #include <stdio.h> #include <stdbool.h> void foo(void) { bool b; if (b) printf("true\n"); if (!b) printf("false\n"); } foo.c This program is UB because b is used without being initialized. But in practice, what do you think might happen when this function is called? void bar(void); void foo(void); int main(void) { bar(); foo(); } main.c void bar(void) { char c = 2; (void)c; } bar.c This is what I get on my computer (Mac OS 10.8.2, gcc 4.7.2) Exercise $ gcc -v gcc version 4.7.2 (GCC) $ gcc foo.c bar.c main.c $ ./a.out true false #include <stdio.h> #include <stdbool.h> void foo(void) { bool b; if (b) printf("true\n"); if (!b) printf("false\n"); } foo.c This program is UB because b is used without being initialized. But in practice, what do you think might happen when this function is called? void bar(void); void foo(void); int main(void) { bar(); foo(); } main.c void bar(void) { char c = 2; (void)c; } bar.c This is what I get on my computer (Mac OS 10.8.2, gcc 4.7.2) Exercise $ gcc -v gcc version 4.7.2 (GCC) $ gcc foo.c bar.c main.c $ ./a.out true false $ #include <stdio.h> #include <stdbool.h> void foo(void) { bool b; if (b) printf("true\n"); if (!b) printf("false\n"); } foo.c This program is UB because b is used without being initialized. But in practice, what do you think might happen when this function is called? void bar(void); void foo(void); int main(void) { bar(); foo(); } main.c void bar(void) { char c = 2; (void)c; } bar.c This is what I get on my computer (Mac OS 10.8.2, gcc 4.7.2) bool b; if (b) printf("b is true\n"); if (!b) printf("b is false\n"); A real story of anything can happen bool b; if (b) printf("b is true\n"); if (!b) printf("b is false\n"); ; the following code assumes that $b is either 0 or 1 load_reg_a $b compare_reg_a 0 jump_equal label1 call_proc print_b_is_true label1: load_reg_a $b xor_reg_a 1 compare_reg_a 0 jump_equal label2 call_proc print_b_is_false label2: A real story of anything can happen bool b; if (b) printf("b is true\n"); if (!b) printf("b is false\n"); ; the following code assumes that $b is either 0 or 1 load_reg_a $b compare_reg_a 0 jump_equal label1 call_proc print_b_is_true label1: load_reg_a $b xor_reg_a 1 compare_reg_a 0 jump_equal label2 call_proc print_b_is_false label2: A real story of anything can happen this is approximately the code generated by one actual version of gcc, try to imagine what will happen if the garbage value of b is 2 bool b; if (b) printf("b is true\n"); if (!b) printf("b is false\n"); ; the following code assumes that $b is either 0 or 1 load_reg_a $b compare_reg_a 0 jump_equal label1 call_proc print_b_is_true label1: load_reg_a $b xor_reg_a 1 compare_reg_a 0 jump_equal label2 call_proc print_b_is_false label2: b is true b is false A real story of anything can happen this is approximately the code generated by one actual version of gcc, try to imagine what will happen if the garbage value of b is 2 #include <stdio.h> #include <string.h> struct X { int a; char b; int c; }; int main(void) { struct X a = {42,'a',1337}; struct X b = {42,'a',1337}; if (memcmp(&a, &b, sizeof a) == 0) printf("equal\n"); else printf("not equal\n"); } a few words about memory #include <stdio.h> #include <string.h> struct X { int a; char b; int c; }; int main(void) { struct X a = {42,'a',1337}; struct X b = {42,'a',1337}; if (memcmp(&a, &b, sizeof a) == 0) printf("equal\n"); else printf("not equal\n"); } This might happen: $ cc -O2 foo.c && ./a.out equal $ cc -O3 foo.c && ./a.out not equal $ a few words about memory #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } a few words about memory #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } a few words about memory On my machine (Mac OS 10.8.2 x86_64): #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c a few words about memory On my machine (Mac OS 10.8.2 x86_64): #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out a few words about memory On my machine (Mac OS 10.8.2 x86_64): #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 a few words about memory On my machine (Mac OS 10.8.2 x86_64): #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 1 a few words about memory On my machine (Mac OS 10.8.2 x86_64): #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 1 12 a few words about memory On my machine (Mac OS 10.8.2 x86_64): #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 1 12 a few words about memory On my machine (Mac OS 10.8.2 x86_64): $ cc -fpack-struct foo.c #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 1 12 a few words about memory On my machine (Mac OS 10.8.2 x86_64): $ cc -fpack-struct foo.c $ ./a.out #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 1 12 a few words about memory On my machine (Mac OS 10.8.2 x86_64): $ cc -fpack-struct foo.c $ ./a.out 4 #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 1 12 a few words about memory On my machine (Mac OS 10.8.2 x86_64): $ cc -fpack-struct foo.c $ ./a.out 4 1 #include <stdio.h> struct X { int a; char b; int c; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 1 12 a few words about memory On my machine (Mac OS 10.8.2 x86_64): $ cc -fpack-struct foo.c $ ./a.out 4 1 9 a a a a b c c c c a a a a b . . . c c c c packed struct memory aligned struct X { int a; char b; int c; }; sizeof(struct X) == 9 sizeof(struct X) == 12 a a a a b c c c c a a a a b . . . c c c c packed struct memory aligned struct X { int a; char b; int c; }; sizeof(struct X) == 9 sizeof(struct X) == 12 Imagine how the assembly code for this snippet would look like: void foo(struct X * x) { x->c += 42; } a a a a b c c c c a a a a b . . . c c c c packed struct memory aligned struct X { int a; char b; int c; }; sizeof(struct X) == 9 sizeof(struct X) == 12 ? Imagine how the assembly code for this snippet would look like: void foo(struct X * x) { x->c += 42; } #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } a few words about memory #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } a few words about memory $ cc -fpack-struct foo.c #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } a few words about memory $ cc -fpack-struct foo.c $ ./a.out #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } a few words about memory $ cc -fpack-struct foo.c $ ./a.out 4 #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } a few words about memory $ cc -fpack-struct foo.c $ ./a.out 4 1 #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } a few words about memory $ cc -fpack-struct foo.c $ ./a.out 4 1 8 #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } a few words about memory $ cc -fpack-struct foo.c $ ./a.out 4 1 8 17 #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c a few words about memory $ cc -fpack-struct foo.c $ ./a.out 4 1 8 17 #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out a few words about memory $ cc -fpack-struct foo.c $ ./a.out 4 1 8 17 #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 a few words about memory $ cc -fpack-struct foo.c $ ./a.out 4 1 8 17 #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 1 a few words about memory $ cc -fpack-struct foo.c $ ./a.out 4 1 8 17 #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 1 8 a few words about memory $ cc -fpack-struct foo.c $ ./a.out 4 1 8 17 #include <stdio.h> struct X { int a; char b; int c; char * p; }; int main(void) { printf("%zu\n", sizeof(int)); printf("%zu\n", sizeof(char)); printf("%zu\n", sizeof(char *)); printf("%zu\n", sizeof(struct X)); } $ cc foo.c $ ./a.out 4 1 8 24 a few words about memory $ cc -fpack-struct foo.c $ ./a.out 4 1 8 17 #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c So whats wrong with this code? #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c So whats wrong with this code? It is crap code #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c So whats wrong with this code? It is crap code The standard says that this is invalid code #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c So whats wrong with this code? It is crap code The standard says that this is invalid code Update a variable multiple times between two semicolons #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c So whats wrong with this code? It is crap code The standard says that this is invalid code Update a variable multiple times between two semicolons Its undened behavior because: Between two sequence points, an object is modied more than once, or is modied and the prior value is read other than to determine the value to be stored the you modify and use the value of a variable twice between sequence points #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c So whats wrong with this code? It is crap code The standard says that this is invalid code Update a variable multiple times between two semicolons Its undened behavior because: Between two sequence points, an object is modied more than once, or is modied and the prior value is read other than to determine the value to be stored the you modify and use the value of a variable twice between sequence points In C (and C++), unlike most other languages, the order in which subexpressions are evaluated and the order in which side effects take place, except as specied for the function-call (), &&, ||, ?:, and comma operators, is unspecied. Therefore the expression i + v[++i] + v[++i] does not make sense. #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c So whats wrong with this code? It is crap code The standard says that this is invalid code Update a variable multiple times between two semicolons Its undened behavior because: Between two sequence points, an object is modied more than once, or is modied and the prior value is read other than to determine the value to be stored the you modify and use the value of a variable twice between sequence points In C (and C++), unlike most other languages, the order in which subexpressions are evaluated and the order in which side effects take place, except as specied for the function-call (), &&, ||, ?:, and comma operators, is unspecied. Therefore the expression i + v[++i] + v[++i] does not make sense. #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c So whats wrong with this code? It is crap code The standard says that this is invalid code Update a variable multiple times between two semicolons Its undened behavior because: Between two sequence points, an object is modied more than once, or is modied and the prior value is read other than to determine the value to be stored the you modify and use the value of a variable twice between sequence points In C (and C++), unlike most other languages, the order in which subexpressions are evaluated and the order in which side effects take place, except as specied for the function-call (), &&, ||, ?:, and comma operators, is unspecied. Therefore the expression i + v[++i] + v[++i] does not make sense. #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c So whats wrong with this code? It is crap code The standard says that this is invalid code Update a variable multiple times between two semicolons Its undened behavior because: Between two sequence points, an object is modied more than once, or is modied and the prior value is read other than to determine the value to be stored the you modify and use the value of a variable twice between sequence points In C (and C++), unlike most other languages, the order in which subexpressions are evaluated and the order in which side effects take place, except as specied for the function-call (), &&, ||, ?:, and comma operators, is unspecied. Therefore the expression i + v[++i] + v[++i] does not make sense. #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c So whats wrong with this code? It is crap code The standard says that this is invalid code Update a variable multiple times between two semicolons Its undened behavior because: Between two sequence points, an object is modied more than once, or is modied and the prior value is read other than to determine the value to be stored the you modify and use the value of a variable twice between sequence points In C (and C++), unlike most other languages, the order in which subexpressions are evaluated and the order in which side effects take place, except as specied for the function-call (), &&, ||, ?:, and comma operators, is unspecied. Therefore the expression i + v[++i] + v[++i] does not make sense. ? #include <stdio.h> int main(void) { int v[] = {0,2,4,6,8}; int i = 1; int n = i + v[++i] + v[++i]; printf("%d\n", n); } foo.c So whats wrong with this code? It is crap code The standard says that this is invalid code Update a variable multiple times between two semicolons Its undened behavior because: Between two sequence points, an object is modied more than once, or is modied and the prior value is read other than to determine the value to be stored the you modify and use the value of a variable twice between sequence points In C (and C++), unlike most other languages, the order in which subexpressions are evaluated and the order in which side effects take place, except as specied for the function-call (), &&, ||, ?:, and comma operators, is unspecied. Therefore the expression i + v[++i] + v[++i] does not make sense. ? But, seriously, who is releasing code with undened behavior? But, seriously, who is releasing code with undened behavior? But, seriously, who is releasing code with undened behavior? But, seriously, who is releasing code with undened behavior? But, seriously, who is releasing code with undened behavior? But, seriously, who is releasing code with undened behavior? But, seriously, who is releasing code with undened behavior? ... /* if both are imag, store value, otherwise store 0.0 */ if (!(li && ri)) { tfree(r); r = bcon(0); } p = buildtree(ASSIGN, l, r); p->n_type = p->n_type += (FIMAG-FLOAT); .... snippet from pftn.c in pcc 1.0.0.RELEASE 20110221 But, seriously, who is releasing code with undened behavior? ... /* if both are imag, store value, otherwise store 0.0 */ if (!(li && ri)) { tfree(r); r = bcon(0); } p = buildtree(ASSIGN, l, r); p->n_type = p->n_type += (FIMAG-FLOAT); .... snippet from pftn.c in pcc 1.0.0.RELEASE 20110221 But, seriously, who is releasing code with undened behavior? Its undened behavior because: Between two sequence points, an object is modied more than once, or is modied and the prior value is read other than to determine the value to be stored the you modify and use the value of a variable twice between sequence points ... /* if both are imag, store value, otherwise store 0.0 */ if (!(li && ri)) { tfree(r); r = bcon(0); } p = buildtree(ASSIGN, l, r); p->n_type = p->n_type += (FIMAG-FLOAT); .... snippet from pftn.c in pcc 1.0.0.RELEASE 20110221 C and C++ are not really high level languages, they are more like portable assemblers. When programming in C and C++ you must have a understanding of what happens under the hood! And if you dont have a decent understanding of it, then you are doomed to create lots of bugs... C and C++ are not really high level languages, they are more like portable assemblers. When programming in C and C++ you must have a understanding of what happens under the hood! And if you dont have a decent understanding of it, then you are doomed to create lots of bugs... But if you do have a useful mental model of what happens under the hood, then... http://www.sharpshirter.com/assets/images/sharkpunchashgrey1.jpg ! The spirit of C trust the programmer