¿Cuál es el uso idiomático de bloques arbitrarios en C?

15

Un bloque es una lista de declaraciones que se ejecutarán. Ejemplos de dónde aparecen los bloques en C son después de una declaración while y en declaraciones if

while( boolean expression)
    statement OR block

if (boolean expression)
    statement OR block

C también permite que un bloque se anide en un bloque. Puedo usar esto para reutilizar nombres de variables, supongamos que realmente me gusta 'x'

int x = 0;
while (x < 10)
{
    {
        int x = 5;
        printf("%d",x)
    }
    x = x+1;
}

imprimirá el número 5 diez veces. Supongo que podría ver situaciones en las que es deseable mantener bajo el número de nombres de variables. Quizás en macro expansión. Sin embargo, no puedo ver ninguna razón difícil para necesitar esta función. ¿Alguien puede ayudarme a comprender los usos de esta función al proporcionar algunas expresiones idiomáticas donde se usa?

Jonathan Gallagher
fuente
1
Honestamente, solo estoy tratando de entender la sintaxis de C, y tengo curiosidad.
Jonathan Gallagher
El espíritu de C es confiar en el programador. El programador tiene el poder a su disposición para hacer algo grandioso ... o hacer algo terrible. Personalmente, no me gustan los nombres de variables sobrecargados, pero otro programador puede. Al final del día, si estamos logrando lo que se supone que debemos hacer con un mínimo de errores ... ¿por qué deberíamos discutir?
Fiddling Bits
1
Esa es la sensación que tengo de C. Estoy a favor de la sintaxis que admite diferentes estilos de codificación (siempre y cuando la semántica del lenguaje esté bien). Es solo que ... Vi esto, y mi respuesta inmediata fue que podía aplicar una transformación de fuente a fuente cambiando el nombre de todas las variables en un bloque con nombres nuevos y aplanar el bloque por completo. Cada vez que pienso que podría deshacerme de algo, supongo que hay algo que me he perdido.
Jonathan Gallagher
Momento curioso, como programador que no es C, me encontré con esta sintaxis hoy en código C y tenía curiosidad por lo que era. Me alegra que lo hayas preguntado.
Brandon

Respuestas:

8

La idea no es mantener baja la cantidad de nombres de variables o alentar la reutilización de nombres, sino limitar el alcance de las variables. Si usted tiene:

int x = 0;
//...
{
    int y = 3;
    //...
}
//...

entonces el alcance de yse limita al bloque, lo que significa que puede olvidarse antes o después del bloque. Verá que esto se usa con mayor frecuencia en relación con bucles y condicionales. También lo ve más a menudo en lenguajes tipo C como C ++, donde una variable que está fuera de alcance causa su destrucción.

Caleb
fuente
Creo que el bloqueo ocurre naturalmente con condicionales, bucles y cuerpos funcionales. Lo que tengo curiosidad es que en C, puedo colocar un bloque en cualquier lugar. Su segundo comentario sobre C ++ es interesante: dejar un alcance causa destrucción. ¿Esto se refiere solo a la recolección de basura, o es el siguiente otro uso: podría tomar un cuerpo de función, que tiene "secciones" distintas y usar bloques para controlar la huella de la memoria al desencadenar la destrucción del espacio variable?
Jonathan Gallagher
1
Hasta donde sé, C asignará previamente toda la memoria para todas las variables en la función. Hacer que salgan del alcance a mitad de camino a través de una función no tendrá beneficios de rendimiento en C. De manera similar, hacer que las variables ingresen al alcance "solo a veces" no cambiará la huella de memoria durante cualquier llamada a la función
Gankro
@Gankro Puede tener un impacto si hay múltiples ámbitos anidados exclusivos. Un compilador puede reutilizar una porción de memoria preasignada única para las variables en cada uno de estos ámbitos. Por supuesto, la razón por la que no viene a la mente es que si un solo alcance arbitrario ya es una señal que probablemente necesite extraer a una función, dos o más ámbitos arbitrarios son definitivamente una buena indicación de que necesita refactorizar. Aún así, aparece de vez en cuando como una solución sensata en cosas como cambiar bloques de cajas.
TNE
16

Porque en los viejos tiempos de C las nuevas variables solo podían declararse en un nuevo bloque.

De esta forma, los programadores podrían introducir nuevas variables en el medio de una función sin filtrarla y minimizar el uso de la pila.

Con los optimizadores de hoy es inútil y una señal de que debe considerar extraer el bloque en su propia función.

En una declaración de cambio, es útil encerrar los casos en sus propios bloques para evitar la doble declaración.

En C ++ es muy útil, por ejemplo, para los protectores de bloqueo RAII y para garantizar que los destructores ejecuten el bloqueo de liberación cuando la ejecución se salga del alcance y sigan haciendo otras cosas fuera de la sección crítica.

monstruo de trinquete
fuente
2
+1 para los guardias de bloqueo RAII. Dicho esto, ¿no podría este mismo concepto también ser útil para la desasignación de búfer de pila de gran tamaño dentro de partes de una rutina? Nunca lo he hecho, pero suena como algo que definitivamente podría ocurrir en algún código incrustado ...
J Trana
2

No lo vería como bloques "arbitrarios". No es una característica que signifique tanto para el uso del desarrollador, pero la forma en que C usa bloques permite usar la misma construcción de bloque en varios lugares con la misma semántica. Un bloque (en C) es un nuevo alcance, y las variables que lo dejan se eliminan. Esto es uniforme independientemente de cómo se use el bloque.

En otros idiomas, este no es el caso. Esto tiene la ventaja de permitir menos abuso como muestra, pero la desventaja de que los bloques se comportan de manera diferente dependiendo del contexto en el que se encuentren.

Raramente he visto bloques independientes utilizados en C o C ++, a menudo cuando hay una estructura grande o un objeto que representa una conexión o algo de lo que desea forzar la destrucción. Por lo general, esto es una pista de que su función está haciendo demasiadas cosas y / o es demasiado larga.

Telastyn
fuente
1
Desafortunadamente, veo regularmente bloques independientes, en casi todos los casos porque la función está haciendo demasiado y / o es demasiado larga.
mattnz
También veo y escribo regularmente bloques independientes en C ++, pero únicamente para forzar la destrucción de envoltorios alrededor de bloqueos y otros recursos compartidos.
J Trana
2

Tienes que darte cuenta, los principios de programación que parecen obvios ahora no siempre fueron así. Las mejores prácticas de C dependen en gran medida de la antigüedad de sus ejemplos. Cuando se introdujo C por primera vez, se consideraba demasiado ineficiente dividir el código en funciones pequeñas. Dennis Ritchie básicamente mintió y dijo que las llamadas a funciones eran realmente eficientes en C (no lo eran en ese momento), que fue lo que hizo que la gente comenzara a usarlas más, aunque de alguna manera los programadores de C nunca pasaron por completo una cultura de optimización prematura.

Que es una buena práctica de programación aún hoy en día para limitar el alcance de las variables de lo más pequeño posible. Hoy en día, generalmente lo hacemos creando una nueva función, pero si las funciones se consideran costosas, la introducción de un nuevo bloque es una forma lógica de limitar su alcance sin la sobrecarga de una llamada a la función.

Sin embargo, comencé a programar en C hace más de 20 años, cuando tenía que declarar todas sus variables en la parte superior de un alcance, y no recuerdo que el sombreado de variables como ese haya sido considerado un buen estilo. Redeclarando en dos bloques, uno tras otro, como en una declaración de cambio, sí, pero sin sombrear. Tal vez si la variable ya se usó y el nombre específico era muy idiomático para la API a la que está llamando, como desty srcen strcpy, por ejemplo.

Karl Bielefeldt
fuente
1

Los bloques arbitrarios son útiles para introducir variables intermedias que solo se usan en casos especiales de un cálculo.

Este es un patrón común en la informática científica, donde los procedimientos numéricos suelen:

  1. confiar en muchos parámetros o cantidades intermedias;
  2. tener que lidiar con muchos casos especiales.

Debido al segundo punto, es útil introducir variables temporales de alcance limitado, que se logran usando un bloque arbitrario o introduciendo una función auxiliar.

Si bien la introducción de una función auxiliar puede parecer una obviedad o una mejor práctica a seguir ciegamente, en realidad hay pocos beneficios para hacerlo en esta situación particular.

Debido a que hay muchos parámetros y cantidades intermedias, queremos introducir una estructura para pasarlos a la función auxiliar.

Pero, dado que queremos ser consecuentes con nuestras prácticas, no introduciremos solo una función auxiliar sino varias. Entonces, si introducimos estructuras ad-hoc que transmiten parámetros para cada función, que introducen una gran cantidad de sobrecarga de código para mover los parámetros de un lado a otro, o si introducimos uno, los regirá a todos la estructura de la hoja de trabajo, que contiene todas nuestras variables pero parece un paquete de bits sin consistencia, donde en cualquier momento solo la mitad de los parámetros tienen un significado interesante.

Por lo tanto, estas estructuras auxiliares son típicamente engorrosas y usarlas significa elegir entre hinchar código o introducir una abstracción cuyo alcance es demasiado amplio y debilitar el significado del programa, en lugar de fortalecerlo .

La introducción de funciones auxiliares podría facilitar las pruebas unitarias del programa mediante la introducción de una granularidad de prueba más fina, pero la combinación de las pruebas unitarias para los procedimientos de bajo nivel y las pruebas de regresión en forma de comparaciones (con numdiff) de trazas numéricas de los procedimientos hace un trabajo igualmente bueno .

usuario40989
fuente
0

En general, la reutilización de nombres de variables de esta manera causa demasiada confusión a los futuros lectores de su código. Es mejor simplemente nombrar la variable interior como otra cosa. De hecho, el lenguaje C # específicamente prohíbe el uso de variables de esta manera.

Pude ver cómo el uso de bloques anidados en expansiones de macro sería útil para evitar colisiones de nombres variables.

Robert Harvey
fuente
0

Es para determinar cosas que normalmente no tienen alcance a ese nivel. Esto es extremadamente raro, y en general la refactorización es una mejor opción, pero me he encontrado con una o dos veces en el pasado en declaraciones de cambio:

switch(foo) {
   case 1:
      {
         // bar
      }
   case 2:
   case 3:
      // baz
      break;
   case 4:
   case 5:
      // bang
      break;
}

Cuando considere el mantenimiento, la refactorización de estos debe equilibrarse con tener todas las implementaciones en una fila, siempre que cada una tenga solo un par de líneas. Puede ser más fácil tenerlos todos juntos, en lugar de una lista de nombres de funciones.

En mi caso, si recuerdo correctamente, la mayoría de los casos fueron ligeras variaciones del mismo código, excepto que uno de ellos era idéntico al otro, solo que con un preprocesamiento adicional. Un modificador terminó siendo la forma más simple de tomar el código, y el alcance adicional permitía el uso de variables locales adicionales de las que no tenía que preocuparse (y los nombres de variables que se superponían accidentalmente con uno definido antes del modificador, pero usado más tarde).

Tenga en cuenta que la declaración de cambio es simplemente el uso que he encontrado. Estoy seguro de que también se puede aplicar en otros lugares, pero no recuerdo haberlos visto efectivamente utilizados de esa manera.

Izkata
fuente