Q:

Optimizing code by using registers, but what if they are not enough in number?

One way to optimize code is to minimize the access to arrays and use variables instead, because that way we use registers instead of loading data to cache memory.
For example, if in a loop I'm going to use 3 times a data from an array, say radius[i], I'd better declare a variable tmp, and give it the value of radius[i]. Every time I need to call radius[i] I'll use tmp instead.
The problem is, the number of registers is limited (to 8 in some cases, it depends on the architecture).
What if I have more variables than the number of registers? Will the usage of variables in this case slow down my program more than having access to cache memory?
I am coding in c language.

A:

Having more variables than registers isn't necessarily a problem. If a variable's value isn't used after a certain point in the function, the compiler can use that register for another variable. Even when there's more variables in use at a certain point than there are registers, the compiler will probably do a better job of figuring out the order in which they need to be moved in and out of registers than you.
The point of a high level language is to abstract away those details. I know if you program in C you're probably concerned about performance, but C is also an unsafe language, which means a mistake will take execution off the rails and result in undefined behavior. Write code that is simple and easy to follow. Forget about that kind of micro-optimization unless you have a clear performance goal, you've profiled the application, confirmed you're not reaching that goal, and you've identified the specific part of the code that needs optimizing. And in that case, there is no definitive answer we can give you - you'll have to try different variations of the code, profile, and see which one ends up being faster on your particular combination of compiler, OS, and architecture.

A:

This is called register allocation, and the compiler/optimiser will take care of it for you. Just make sure to turn optimisation on. If you want to see what it does, just compile to assembler ... and then learn to read and understand assembler. The quality of implementation will vary, but in general you don't need to think about this unless you have a specific performance problem.
The only time it might be worth thinking about this up-front, is when you know an array can't be aliased, but it will be hard for the compiler to prove this. Eg,
double radius[3];
double *possible_alias = get_ptr();
...
for (int i=0; i<3; ++i) {
    *possible_alias *= radius[i];
    do_something_else(radius[i]);
}

if you know the pointer assignment won't change radius[i], but the compiler may be unable to rely on it, you could write
    double r = radius[i];
    *possible_alias *= r;
    do_something_else(r);

In general, don't worry about this sort of micro optimisation unless you find a specific performance problem. Reserve your efforts for writing code that's clear, easy to debug and understand, and choosing the right data structures and algorithms.
As for how to profile your code, that's a massive topic, and the support depends on your platform and toolchain. There are compiled-in sampling profilers (like gprof, for the GCC toolchain), post-hoc profilers (like Intel's VTune and the free valgrind), or you can just time whole runs or write your own timing points manually. None of this is likely to be worth the effort unless your program is objectively too slow in the first place.

Note to OP: the discussion in comments below is about the situations in which the CPU may be able to optimise this at run time, even if the compiler couldn't. At least on modern x86 platforms, it just reduces even further the value of thinking about this.

