There are two cases: at file scope (i.e. for a global declaration), and in a function.
In a function, the declaration
int i; does two things: it declares a variable called
i whose type is
int, and it reserves some storage in memory to put a value of type
int. What it does not do is give the variable a value. The storage used by
i will still contain whatever garbage was there before. You need to initialize the variable, i.e. assign a value to it, before you can read a value from it. Good compilers will warn you if you don't initialize the variable.
At file scope,
int i also declares a variable called
i. The rest depends on other things: this is known as a tentative definition. You can have multiple such declarations in your file. At most one of these is allowed to have an initializer, making it a full-fleged definition. If none of the declarations of
i at file scope have an initializer, the declaration is also a definition, and there is an implicit initialization to
/* ... more code ...*/
is valid, and
i will be initialized to 0 (assuming these are the only declarations of
i at file scope). Whereas:
int i = 3;
is also valid, and
i will be initialized to 3 when the program starts.
In practice, at file scope, there's often a difference between leaving the initialization implicit and explicitly initializing to 0. Many compilers will store an explicit 0 in the binary, but let the operating system initialize implicit zeroes automatically when the program is loaded. Don't worry about this unless you have a large global array (which shouldn't happen often) or you work on tiny embedded systems.