Qual è l'uso idiomatico dei blocchi arbitrari in C?


15

Un blocco è un elenco di istruzioni da eseguire. Esempi di casi in cui vengono visualizzati i blocchi in C sono dopo un'istruzione while e in istruzioni if

while( boolean expression)
    statement OR block

if (boolean expression)
    statement OR block

C consente anche di annidare un blocco in un blocco. Posso usarlo per riutilizzare i nomi delle variabili, supponiamo che mi piaccia davvero 'x'

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

stamperà il numero 5 dieci volte. Immagino di poter vedere situazioni in cui è desiderabile mantenere basso il numero di nomi di variabili. Forse in macro espansione. Tuttavia, non riesco a vedere alcun motivo difficile per aver bisogno di questa funzione. Qualcuno può aiutarmi a capire gli usi di questa funzione fornendo alcuni modi di dire dove viene utilizzata.


1
Onestamente, sto solo cercando di capire la sintassi C e sono curioso.
Jonathan Gallagher,

Lo spirito di C è fidarsi del programmatore. Il programmatore ha il potere di fare qualcosa di eccezionale ... o di fare qualcosa di terribile. Personalmente, non mi piacciono i nomi di variabili sovraccaricati, ma un altro programmatore potrebbe. Alla fine della giornata se stiamo realizzando ciò che dovremmo fare con un numero minimo di bug ... perché dovremmo discutere?
Fiddling Bits

1
Questa è la sensazione che provo da C. Sono tutto per la sintassi che supporta diversi stili di codifica (anche se la semantica della lingua va bene). È solo che ... Ho visto questo, e la mia risposta immediata è stata, ho potuto applicare una trasformazione da sorgente a sorgente rinominando tutte le variabili in un blocco con nomi nuovi e appiattendo completamente il blocco. Ogni volta che penso di poter sbarazzarmi di qualcosa, presumo che ci sia qualcosa che mi è sfuggito.
Jonathan Gallagher il

Tempismo divertente, come programmatore non C mi sono imbattuto in questa sintassi oggi nel codice C ed ero curioso di sapere a cosa servisse. Sono contento che tu l'abbia chiesto.
Brandon,

Risposte:


8

L'idea non è quella di mantenere basso il numero dei nomi delle variabili o di incoraggiare il riutilizzo dei nomi, ma piuttosto di limitare l'ambito delle variabili. Se hai:

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

quindi l'ambito di yè limitato al blocco, il che significa che puoi dimenticartene prima o dopo il blocco. Lo vedi usato più spesso in connessione con loop e condizionali. Lo vedi anche più spesso in linguaggi simil-C come C ++, dove una variabile che esce dall'ambito provoca la sua distruzione.


Penso che il blocco si verifichi naturalmente con condizionali, loop e corpi funzionali. Ciò di cui sono curioso è che in C posso posizionare un blocco ovunque. Il tuo secondo commento su C ++ è interessante: lasciare un ambito causa la distruzione. Si riferisce solo alla garbage collection, o è il seguente altro uso: potrei prendere un corpo di funzione, che ha "sezioni" distinte e usare blocchi per controllare il footprint di memoria innescando la distruzione dello spazio variabile?
Jonathan Gallagher il

1
Per quanto ne so, C preallocerà tutta la memoria per tutte le variabili nella funzione. La loro uscita dall'ambito durante una funzione non avrà alcun vantaggio in termini di prestazioni in C. Allo stesso modo, se le variabili entrano nell'ambito "solo a volte" non cambierà il footprint di memoria durante qualsiasi chiamata alla funzione
Gankro,

@Gankro Può avere un impatto se ci sono più ambiti nidificati esclusivi. Un compilatore può riutilizzare un pezzo di memoria preallocato univoco per le variabili in ciascuno di questi ambiti. Naturalmente, il motivo per cui non viene in mente che se un singolo ambito arbitrario è già un segno che probabilmente è necessario estrarre in una funzione, due o più ambiti arbitrari sono sicuramente una buona indicazione che è necessario refactoring. Tuttavia, si presenta come una soluzione sana in cose come cambiare i blocchi di caso di volta in volta.
TNE

16

Perché in passato giorni di C nuove variabili potevano essere dichiarate solo in un nuovo blocco.

In questo modo i programmatori potrebbero introdurre nuove variabili nel mezzo di una funzione senza perderla e minimizzare l'uso dello stack.

Con gli ottimizzatori di oggi è inutile e un segno che devi considerare di estrarre il blocco nella sua stessa funzione.

In un'istruzione switch, è utile racchiudere i casi nei propri blocchi per evitare una doppia dichiarazione.

In C ++ è molto utile, ad esempio, per le protezioni di blocco RAII e per garantire che i distruttori eseguano il blocco di rilascio quando l'esecuzione esce dall'ambito e continua a fare altre cose al di fuori della sezione critica.


2
+1 per le protezioni della serratura RAII. Detto questo, questo stesso concetto non potrebbe essere utile anche per la deallocazione del buffer dello stack di grandi dimensioni all'interno di parti di una routine? Non l'ho mai fatto, ma suona come qualcosa che potrebbe sicuramente accadere in qualche codice incorporato ...
J Trana,

2

Non lo guarderei come blocchi "arbitrari". Non è una funzionalità pensata tanto per l'uso da parte degli sviluppatori, ma il modo in cui C utilizza i blocchi consente di utilizzare lo stesso costrutto di blocchi in un numero di posizioni con la stessa semantica. Un blocco (in C) è un nuovo ambito e le variabili che lo lasciano vengono eliminate. Questo è uniforme indipendentemente da come viene utilizzato il blocco.

In altre lingue, non è così. Ciò ha il vantaggio di consentire un minor numero di abusi come si vede, ma lo svantaggio che i blocchi si comportano diversamente a seconda del contesto in cui si trovano.

Raramente ho visto blocchi autonomi usati in C o C ++ - spesso quando c'è una grande struttura o un oggetto che rappresenta una connessione o qualcosa di cui vuoi forzare la distruzione. Di solito questo è un suggerimento che la tua funzione sta facendo troppe cose e / o è troppo lunga.


1
Sfortunatamente vedo regolarmente blocchi autonomi - in quasi tutti i casi perché la funzione sta facendo troppo e / o è troppo lunga.
mattnz,

Inoltre vedo e scrivo regolarmente blocchi autonomi in C ++, ma solo per forzare la distruzione di wrapper attorno a blocchi e altre risorse condivise.
J Trana,

2

Devi capire che i principi di programmazione che sembrano ovvi ora non sono sempre stati così. Le migliori pratiche di C dipendono in larga misura dall'età dei tuoi esempi. Quando C fu introdotto per la prima volta, suddividere il codice in piccole funzioni era considerato troppo inefficiente. Dennis Ritchie ha sostanzialmente mentito e ha detto che le chiamate di funzione erano davvero efficienti in C (non lo erano all'epoca), il che è stato ciò che ha spinto le persone a usarle di più, sebbene i programmatori C in qualche modo non abbiano mai superato completamente una cultura di ottimizzazione prematura.

Si tratta di una buona pratica di programmazione anche oggi di limitare la portata delle variabili più piccolo possibile. Al giorno d'oggi, di solito lo facciamo creando una nuova funzione, ma se le funzioni sono considerate costose, l'introduzione di un nuovo blocco è un modo logico per limitare l'ambito senza l'overhead di una chiamata di funzione.

Tuttavia, ho iniziato a programmare in C oltre 20 anni fa, quando dovevi dichiarare tutte le tue variabili in cima a un ambito, e non ricordo l'ombreggiatura delle variabili come quella mai considerata un buon stile. Riscrivere in due blocchi uno dopo l'altro come in un'istruzione switch, sì, ma non in ombra. Forse se la variabile fosse già stata utilizzata e il nome specifico fosse altamente idiomatico per l'API che stai chiamando, come deste srcin strcpy, ad esempio.


1

I blocchi arbitrari sono utili per introdurre variabili intermedie che vengono utilizzate solo in casi speciali di calcolo.

Questo è un modello comune nel calcolo scientifico, in cui le procedure numeriche in genere:

  1. fare affidamento su molti parametri o quantità intermedie;
  2. avere a che fare con molti casi speciali.

A causa del secondo punto, è utile introdurre variabili temporanee di portata limitata, ottenute utilizzando un blocco arbitrario o introducendo una funzione ausiliaria.

Mentre l'introduzione di una funzione ausiliaria può sembrare un gioco da ragazzi o una buona pratica da seguire alla cieca, in realtà ci sono pochi benefici da fare in questa particolare situazione.

Poiché ci sono molti parametri e quantità intermedie, vogliamo introdurre una struttura per passarli alla funzione ausiliaria.

Ma, poiché vogliamo essere conseguenti alle nostre pratiche, non introdurremo solo una funzione ausiliaria ma diverse. Quindi, se introduciamo strutture ad hoc che trasmettono parametri per ogni funzione, che introducono un sacco di sovraccarico di codice per spostare i parametri avanti e indietro, o introduciamo uno che governerà tutte le strutture del foglio di lavoro, che contiene tutte le nostre variabili ma sembra un grabpack di bit senza coerenza, dove in qualsiasi momento solo la metà dei parametri ha un significato interessante.

Pertanto, queste strutture ausiliarie sono in genere ingombranti e usarle significa scegliere tra code-bloat o introdurre un'astrazione il cui ambito è troppo ampio e indebolire il significato del programma, invece di scoraggiarlo .

L'introduzione di funzioni ausiliarie potrebbe facilitare i test unitari del programma introducendo una granularità di test più fine ma combinando i test unitari non per le procedure di basso livello e i test di regressione sotto forma di confronti (con numdiff) di tracce numeriche delle procedure fa un buon lavoro .


0

In generale, il riutilizzo dei nomi delle variabili in questo modo provoca troppa confusione ai futuri lettori del codice. È meglio semplicemente nominare la variabile interna qualcos'altro. In effetti, il linguaggio C # non consente specificamente l'uso di variabili in questo modo.

Ho potuto vedere come l'uso di blocchi nidificati nelle espansioni macro sarebbe utile per prevenire collisioni di nomi variabili.


0

È per scopare cose che normalmente non hanno un ambito a quel livello. Questo è estremamente raro, e in generale il refactoring è una scelta migliore, ma l'ho incontrato una o due volte in passato nelle istruzioni switch:

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

Quando si considera la manutenzione, questi refactoring devono essere bilanciati con tutte le implementazioni di fila, purché ciascuna sia lunga solo un paio di righe. Può essere più semplice averli tutti insieme, piuttosto che un elenco di nomi di funzioni.

Nel mio caso, se ricordo bene, la maggior parte dei casi erano lievi variazioni dello stesso codice, tranne per il fatto che uno di essi era identico a un altro, solo con una preelaborazione aggiuntiva. Uno switch ha finito per essere la forma più semplice da prendere per il codice e l'ambito extra ha permesso l'uso di variabili locali extra che esso e altri casi non dovevano preoccupare (come nomi di variabili che si sovrappongono accidentalmente a uno definito prima dello switch ma usato più tardi).

Nota che l'istruzione switch è semplicemente l'uso di essa che ho incontrato. Sono sicuro che può applicarsi anche altrove, ma non ricordo di averli usati efficacemente in quel modo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.