Mixed declarations and statements appeared in C99. C90 didn't have them.

Other Algol-like languages including Algol itself also separate declarations and statements: Pascal, Modula, Ada, ...

In C90, you can have variables anywhere, but they must be wrapped in a compound statement. This has a number of advantages:

1. You can repeat variables:

{
int yes = 1;
setsockopt(fd, SOL_SOCKET, SO_WHATEVER, &yes);
}

{
int yes = 1;
setsockopt(fd, SOL_SOCKET, SO_OTHER, &yes);
}

2. You can move code around more easily, and move these sub-blocks into their own functions more easily, since they follow "declaration close to use".

3. You control the end of the scope of the variables, not only the beginning. You know that after each of the above two closing braces, the yes variable is no longer in scope. (If there is a yes variable in scope, it must be coming from a parent scope; it is not "leaking" sideways.)

4. If you write a goto which goes around these encapsulated scopes, that goto does not jump into a region where the variable is uninitialized. Compare:

void fn()
{
goto end;
{
int x = 42;
}
end: ;

// x is not in scope here, OK
}

void fn()
{
goto end;
int x = 42;
end: ;
// x is in scope here, but not initialized!
}

For these reasons, I avoid mixing declarations and statements in C programming; the separation is a very good idea. It's enforced in Ada, where it is rationalized with safety arguments. Mixing declarations and statements encourages bugs. It lets you hack dubious solutions into the program without having to think about how its structure could be improved to do it cleanly.

In Common Lisp and similar dialects, you can mix imperative code with variable definitions by using progn.

(let ((x (get-x-object)
(y (progn (perform-side-effect)
(get-y-object)))
(z (get-z-object)))
...)

On a small number of occasions, I've done the above in C, using the comma operator, to avoid mixed declarations and statements:

int x = get_x();
int y = (perform_side_effect(), get_y());

It's a little dirty but not as dirty as mixed declarations and statements.

Also note that there is no need for mixed declarations and statements, if your code is functional! In any of your code that emphasizes functional programming, you should not run into a need for this, because the entire purpose of the feature is to be able to stick a side effect between variable definitions, which has to be sequenced there.

The Scheme language has the feature of defining variables anywhere. You can use the define form in any scope, like inside a function:

(define (fun arg)
(define x 0)
(define y 1)
...)

How this works is that Scheme implementations perform a code walk which transforms these defines into nested let forms. Each sequential body of expressions has to be scanned for the presence of define and treated this way.

It's pretty ugly; you end up with what looks like a self-contained form (define x 0) that is frobbing the surrounding lexical scope, such that another form not enclosed in it depends on its definition of x.

In Common Lisp and related dialects, anything that starts with "(def" is understood to be for top-level definitions only, which work by performing a run-time side effect when they are evaluated. (And so their effect is visible to later forms not due to lexical scope but due to the chronological order of execution.)

There is a strong metaprogramming advantage in having rigid variable binding forms like let. Because let has all the variables in one place, it is easy to interpret and compile. This is one of the things that helps a Lisp-in-Lisp metacircular evaluator be very short. If you have variables defined anywhere, it means that every form that contains statement-like forms must be scanned for those definitions. They are not in a fixed place in the AST: (let vars form ...). That is one big reason why if you implement Scheme's define, you want that to be expanded early, so that everything downstream just sees nice nested lets.