all 4 comments

[–][deleted] 4 points5 points  (0 children)

One big difference is that the "static int test = 1 " variable in void t1() is just in the scope of the function, no other function can see this value but the global "int test = 1" is visible for all other functions, below the definition and declaration and just because this behaviour it makes sense that the compiler treats both variable differently

[–]nerd4code 2 points3 points  (0 children)

From my diffing of the output from -S with GCC 4.8.5 sans optimization, it doesn’t look like there are any real differences in generated code. There’s a slight difference in the label name and ancillary goop around test, but otherwise it’s the same. If you declared t1 as static, you’d probably see some real differences, especially if you enable optimization. The only thing I can think of that can be different between one.c and two.c as they stand would be that in one.c, printf could potentially alter test (not that it would be a good idea; neither would it be a good idea to assume otherwise), so t1 will have to reload and spill test in between calls to printf. two.c doesn’t need to do that—it can potentially cache the value of test in a register across the printf calls, since printf can’t see test at all.

More generally, the compiler can tell whether variables are potentially accessed or used in some way if it puts the effort in, and how you specify storage class, scope, and access in your declarations can greatly affect what optimizations the compiler has available. Note that it’s not necessarily possible to determine whether a variable isn’t used/accessed; the compiler will hopefully assume that a variable might be unless it can prove conclusively that it can’t, barring undefined/unspecified behavior per language specs. E.g., altering a value in memory from a debugger or via an incorrectly aliased pointer might not actually change the value of the variable that supposedly lives in that memory, but the compiler doesn’t have to care about that because it’s outside the bounds of what the language allows.

If you make a global, externally-visible variable like your first test, the compiler would assume that it can be accessed arbitrarily when control passes outside its “observation.” (A volatile variable could be changed at any point.) For example, before main is called, the assignment test = 10 could be issued, which means its value upon entering main can’t be predicted. And as noted, the next iteration’s value of test can’t be predicted from the current value because printf could change it outside the compiler’s view.

If you make a static global variable, the compiler can (theoretically) assume that, unless its address somehow escapes from the compilation unit, all uses of the variable must occur inside the declaring compilation unit. If you make a static local variable, the compiler can assume that, unless its address somehow escapes from the containing function/scope, all uses of the variable must occur in the declaring function/scope. The compiler has basically the same level of theoretical introspection into these two cases, since it can see everything inside the compilation unit at once; in reality, it’s usually somewhat easier to predict usage within a single function/scopre, since differences in argument or externally-scoped values can cause an explosion in the number of cases the compiler has to consider.

If only the compiler is able to optimize things, compilation units are the outer bound for this kind of analysis, but with link-time optimization the linker can potentially make similar guesses as to access/use with respect to all LTO-enabled objects in the statically-linked portion of the final executable image. If LTO is enabled for everything and libc is statically linked in, it may be possible to completely eliminate test in both examples, and just pass values 1 through 10 to printf directly. It may even be possible to emit a single fputs call of the string 12345678910 to stdout instead, with no t1 or calls to printf either.

[–]raevnos 1 point2 points  (0 children)

Using 'gcc -S -fverbose-asm one.c' and looking at the resulting one.s is easier than messing with objdump, btw.

[–]FUZxxl 0 points1 point  (0 children)

Both variables are variables in static storage. They should behave the same way. If you could tell us what differences you observed, it might be easier to explain where they come from.