Domanda

Ho scritto un po ' di C, e riesco a leggere abbastanza bene per avere un'idea generale di ciò che fa, ma ogni volta che ho incontrato una macro, non ha gettato me completamente.Finisco per dover ricordare che la macro è e sostituirlo nella mia testa, come ho letto.Quelli che ho incontrato che erano intuitiva e di facile comprensione sono sempre stati come mini funzioni, così mi sono sempre chiesto perché non erano solo le funzioni.

Posso capire la necessità di definire i diversi tipi di generazione per eseguire il debug o cross-platform costruisce nel preprocessore, ma la capacità di definire arbitrario sostituzioni sembra essere utile solo per fare già una lingua difficile, ancora più difficile da capire.

Perché era un tale complesso preprocessore introdotto per C?E qualcuno ha un esempio di utilizzo è quello che mi farà capire perché sembra ancora essere utilizzato per scopi tutt'altro che semplice se #debug stile compilazioni condizionali?

Edit:

Dopo aver letto un numero di risposte che ho ancora proprio non capisco.La risposta più comune è il codice in linea.Se la parola chiave inline non fare, quindi, è un buon motivo per non farlo, o per l'applicazione ha bisogno di fissaggio.Non capisco perché un intero meccanismo diverso, che significa "davvero inline presente codice" (a parte il codice che viene scritto prima di inline era in giro).Anche io non capisco l'idea che è stato detto che "se è troppo stupido per essere messo in funzione".Sicuramente qualsiasi pezzo di codice che accetta un input e produce un output è meglio metterla in una funzione.Penso di non essere sempre, perché io non sono utilizzati per il micro ottimizzazioni di scrittura C, ma il preprocessore si sente proprio come una soluzione complessa per un paio di semplici problemi.

È stato utile?

Soluzione

Finisco per dover ricordare che la macro è e sostituirlo nella mia testa, come ho letto.

Che sembra si riflettono negativamente sulla denominazione delle macro.Mi viene da supporre che non avrebbe dovuto emulare il preprocessore se si trattasse di un log_function_entry() macro.

Quelli che ho incontrato che erano intuitiva e di facile comprensione sono sempre stati come mini funzioni, così mi sono sempre chiesto perché non erano solo le funzioni.

Di solito dovrebbero essere, a meno che non hanno bisogno di operare sui parametri generici.

#define max(a,b) ((a)<(b)?(b):(a))

funziona su qualsiasi tipo con un < operatore.

Più che solo le funzioni, le macro consentono di eseguire operazioni con i simboli del file di origine.Ciò significa che è possibile creare un nuovo nome di variabile, o di riferimento, il file di origine e il numero di riga della macro è su.

In C99, macro, inoltre, consentono di chiamare variadic funzioni come la printf

#define log_message(guard,format,...) \
   if (guard) printf("%s:%d: " format "\n", __FILE__, __LINE__,__VA_ARGS_);

log_message( foo == 7, "x %d", x)

In cui funziona il formato come printf.Se la protezione è vero, il sistema emette un messaggio con il numero di riga e file stampato il messaggio.Se era una chiamata di funzione, non si sa il file e la riga è chiamata dall', e l'utilizzo di un vaprintf sarebbe un po ' più di lavoro.

Altri suggerimenti

Questo brano riassume il mio punto di vista sulla questione, confrontando diversi modi che C le macro vengono utilizzati, e come implementarli in D.

copiato da DigitalMars.com

Indietro quando C è stato inventato, compilatore la tecnologia primitiva.L'installazione di un testo macro del preprocessore sul fronte fine è un semplice e facile modo per aggiungere molte caratteristiche potenti.Il aumentare la dimensione e la complessità del i programmi hanno illustrato che questi caratteristiche e dotate di molti inerente problemi. D non ha un il preprocessore;ma D offre un più scalabile per risolvere lo stesso problemi.

Macro

Macro del preprocessore aggiungere potenti funzionalità e flessibilità per C.Ma hanno anche un rovescio della medaglia:

  • Le macro non hanno il concetto di ambito;sono validi dal punto di definizione al fine di fonte.Hanno tagliato una fascia tra .h file, annidati codice, etc.Quando #include'ing decine di migliaia di righe di definizioni di macro, diventa problematico per evitare involontarie macro espansioni.
  • Le macro sono sconosciuti al debugger.Cercando di eseguire il debug di un programma simbolico dati è minata dal debugger solo di conoscere la macro espansioni, non le macro se stessi.
  • Macro rendere impossibile per simboleggiare il codice sorgente, come un precedente macro il cambiamento può arbitrariamente rifare gettoni.
  • Puramente testuale base di macro porta a arbitrari e incoerenti di utilizzo, rendendo il codice utilizzando le macro di errori.(Qualche tentativo di risolvere questa è stata introdotta con i modelli in C++.)
  • Le macro sono ancora utilizzati per compensare i deficit della lingua, capacità espressive, come per il "wrapper" file di intestazione.

Ecco un elenco dei più comuni usi per le macro, e che la funzione corrispondente in D:

  1. La definizione di costanti letterali:

    • Il C Il Preprocessore Modo

      #define VALUE 5
      
    • Il D Modo

      const int VALUE = 5;
      
  2. Creazione di un elenco di valori o di bandiere:

    • Il C Il Preprocessore Modo

      int flags:
      #define FLAG_X  0x1
      #define FLAG_Y  0x2
      #define FLAG_Z  0x4
      ...
      flags |= FLAG_X;
      
    • Il D Modo

      enum FLAGS { X = 0x1, Y = 0x2, Z = 0x4 };
      FLAGS flags;
      ...
      flags |= FLAGS.X;
      
  3. Impostazione convenzioni di chiamata di funzione:

    • Il C Il Preprocessore Modo

      #ifndef _CRTAPI1
      #define _CRTAPI1 __cdecl
      #endif
      #ifndef _CRTAPI2
      #define _CRTAPI2 __cdecl
      #endif
      
      int _CRTAPI2 func();
      
    • Il D Modo

      Convenzioni di chiamata può essere specificato in blocchi, quindi non c'è bisogno di cambiare per ogni funzione:

      extern (Windows)
      {
          int onefunc();
          int anotherfunc();
      }
      
  4. Semplice programmazione generica:

    • Il C Il Preprocessore Modo

      Selezione della funzione, l'uso di base di un testo di sostituzione:

      #ifdef UNICODE
      int getValueW(wchar_t *p);
      #define getValue getValueW
      #else
      int getValueA(char *p);
      #define getValue getValueA
      #endif
      
    • Il D Modo

      D consente dichiarazioni di simboli che sono alias di altri simboli:

      version (UNICODE)
      {
          int getValueW(wchar[] p);
          alias getValueW getValue;
      }
      else
      {
          int getValueA(char[] p);
          alias getValueA getValue;
      }
      

Ci sono altri esempi sul DigitalMars sito.

Sono un linguaggio di programmazione (più semplice) di C, quindi sono utili per fare metaprogrammazione in fase di compilazione...in altre parole, è possibile scrivere il codice della macro che genera codice C in meno linee e tempo di scrivere direttamente in C.

Sono anche molto utili per scrivere una funzione di "come" espressioni "polimorfico" o "sovraccarico";ad es.un max macro definita come:

#define max(a,b) ((a)>(b)?(a):(b))

è utile per qualsiasi tipo numerico;e in C non si poteva scrivere:

int max(int a, int b) {return a>b?a:b;}
float max(float a, float b) {return a>b?a:b;}
double max(double a, double b) {return a>b?a:b;}
...

anche se si voleva, perché non è possibile sovraccarico di funzioni.

E per non parlare di compilazione condizionale e file (che sono anche parte del linguaggio macro)...

Macro permettere a qualcuno di modificare il comportamento del programma durante il tempo di compilazione.Considerate questo:

  • C costanti permettono di fissare il comportamento del programma in fase di sviluppo
  • C variabili consentono di modificare il comportamento del programma in fase di esecuzione
  • C le macro consentono di modificare il comportamento del programma in fase di compilazione

Al momento della compilazione significa che il codice non utilizzato non anche andare in binario e che il processo di generazione può modificare i valori, come è integrato con la macro del preprocessore.Esempio:fare ARCH=braccio (si presuppone inoltro definizione di macro cc-DARCH=braccio)

Semplici esempi:(da glibc limiti.h, definire il valore più grande di lungo)

#if __WORDSIZE == 64
#define LONG_MAX 9223372036854775807L
#else
#define LONG_MAX 2147483647L
#endif

Verifica (utilizzando il #define __WORDSIZE) in fase di compilazione se si sta compilando per la versione a 32 o a 64 bit.Con un multilib toolchain, utilizzando i parametri -m32 e -m64 può modificare automaticamente la dimensione in bit.

POSIX (versione richiesta)

#define _POSIX_C_SOURCE 200809L

Le richieste durante il tempo di compilazione POSIX 2008 supporto.La libreria standard può supportare molti (incompatibile) standard, ma con questa definizione, si intende fornire la corretta prototipi di funzione (esempio:getline(), non gets(), etc.).Se la libreria non supportano lo standard può dare un #errore durante la fase di compilazione, invece di schiantarsi durante l'esecuzione, per esempio.

(codificato percorso)

#ifndef LIBRARY_PATH
#define LIBRARY_PATH "/usr/lib"
#endif

Definisce, durante la compilazione di un codificare directory.Potrebbe essere cambiata con -DLIBRARY_PATH=/home/utente/lib, per esempio.Se fosse un const char *, come si fa a configurarlo durante la compilazione ?

(pthread.h, definizioni complesse in fase di compilazione)

# define PTHREAD_MUTEX_INITIALIZER \
  { { 0, 0, 0, 0, 0, 0, { 0, 0 } } }

Grandi pezzi di testo può che altrimenti non sarebbe semplificata può essere dichiarato (sempre in fase di compilazione).Non è possibile fare questo con funzioni o costanti (in fase di compilazione).

Per evitare davvero complicare le cose e per evitare suggerendo poveri di codifica stili, io sono abituato a dare un esempio di codice che compila in diversi, incompatibili, sistemi operativi.Usa la tua croce costruire il sistema, ma deve essere chiaro che il preprocessore permette che senza l'aiuto del sistema di generazione, senza rompere la compilazione a causa di assenza di interfacce.

Infine, riflettere sull'importanza della compilazione condizionale su sistemi embedded, dove la velocità del processore e la memoria sono limitati e i sistemi sono molto eterogenei.

Ora, se si chiede, è possibile sostituire tutte le macro definizioni di costanti e le chiamate di funzione con vere e proprie definizioni ?La risposta è sì, ma non è sufficiente effettuare la necessità di cambiare il comportamento del programma in fase di compilazione andare via.Il preprocessore è ancora necessaria.

Ricordate che le macro (e il pre-processore) provengono dai primi giorni di C.Hanno usato per essere l'UNICO modo per fare inline 'funzioni' (perché, naturalmente, che in linea è molto recenti parole chiave), e sono ancora l'unico modo per FORZA qualcosa per essere sostituite.

Inoltre, le macro sono il solo modo in cui si possono fare trucchi come inserire il file e la riga in costanti di stringa in fase di compilazione.

In questi giorni, molte delle cose che le macro usato per essere l'unico modo per farlo sono meglio gestite attraverso nuovi meccanismi.Ma hanno ancora il loro posto, di volta in volta.

A parte l'inline per l'efficienza e la compilazione condizionale, le macro possono essere utilizzati per aumentare il livello di astrazione di un basso livello di codice C.C davvero non isolare dal nocciolo dettagli di memoria e di gestione delle risorse e l'esatta disposizione dei dati e supporta molto limitate forme di nascondere informazioni e altri meccanismi per la gestione di grandi sistemi.Con le macro, non è più limitato al solo utilizzo dei costrutti di base del linguaggio C:è possibile definire le proprie strutture di dati e la codifica costrutti (comprese le classi e modelli!) mentre ancora nominalmente scrittura C!

Macro del preprocessore realmente offrire un Turing-completo lingua eseguito a tempo di compilazione.Una delle più impressionanti (e un po ' spaventoso) esempi di questo è finito sul C++:il Aumentare Il Preprocessore libreria utilizza il C99/C++98 il preprocessore per costruire (relativamente) sicuro costrutti di programmazione che sono poi ampliato per qualsiasi sottostanti dichiarazioni e codice di input, se il C o il C++.

In pratica, mi raccomando per quanto riguarda il preprocessore di programmazione come ultima risorsa, quando non hai la latitudine ad alto livello costrutti di sicuro lingue.Ma a volte è bene sapere che cosa si può fare se la tua schiena è contro il muro e la donnola stanno chiudendo...!

Da Computer Stupidaggini:

Ho visto questa porzione di codice in un sacco di freeware di gioco programmi per UNIX:

/*
* Valori di Bit.
*/
#define BIT_0 1
#define BIT_1 2
#define BIT_2 4
#define BIT_3 8
#define BIT_4 16
#define BIT_5 32
#define BIT_6 64
#define BIT_7 128
#define BIT_8 256
#define BIT_9 512
#define BIT_10 1024
#define BIT_11 2048
#define BIT_12 4096
#define BIT_13 8192
#define BIT_14 16384
#define BIT_15 32768
#define BIT_16 65536
#define BIT_17 131072
#define BIT_18 262144
#define BIT_19 524288
#define BIT_20 1048576
#define BIT_21 2097152
#define BIT_22 4194304
#define BIT_23 8388608
#define BIT_24 16777216
#define BIT_25 33554432
#define BIT_26 67108864
#define BIT_27 134217728
#define BIT_28 268435456
#define BIT_29 536870912
#define BIT_30 1073741824
#define BIT_31 2147483648

Un modo molto più semplice per ottenere questo risultato è:

#define BIT_0 0x00000001
#define BIT_1 0x00000002
#define BIT_2 0x00000004
#define BIT_3 0x00000008
#define BIT_4 0x00000010
...
#define BIT_28 0x10000000
#define BIT_29 0x20000000
#define BIT_30 0 x 40000000
#define BIT_31 0x80000000

Un modo più semplice è quello di consentire al compilatore di fare i calcoli:

#define BIT_0 (1)
#define BIT_1 (1 << 1)
#define BIT_2 (1 << 2)
#define BIT_3 (1 << 3)
#define BIT_4 (1 << 4)
...
#define BIT_28 (1 << 28)
#define BIT_29 (1 << 29)
#define BIT_30 (1 << 30)
#define BIT_31 (1 << 31)

Ma perché andare a tutti i problemi di definizione di 32 costanti?Il linguaggio C, anche con parametri macro.Tutti si ha realmente bisogno è:

#define BIT(x) (1 << (x))

Comunque, mi chiedo se il tizio che ha scritto il codice originale usato una calcolatrice o semplicemente calcolato tutto su carta.

Questo è solo un possibile utilizzo di Macro.

Uno dei casi in cui le macro davvero brillare è quando si fa la generazione del codice con loro.

Ho usato per lavorare su un vecchio C++ sistema che utilizza un sistema di plugin con il suo modo per passare i parametri del plugin (Utilizzando una mappa personalizzata-come struttura).Alcuni semplici macro sono stati utilizzati per essere in grado di affrontare questa stranezza e ci ha permesso di usare C++ le classi e le funzioni con parametri normali nei plugin senza troppi problemi.Tutta la colla codice che viene generato da macro.

Vorrei aggiungere a che cosa è già stato detto.

Perché la macro opera su testo di sostituzioni che permettono di fare tante cose molto utili che non sarebbe possibile fare uso delle funzioni.

Qui un paio di casi in cui le macro possono essere davvero utili:

/* Get the number of elements in array 'A'. */
#define ARRAY_LENGTH(A) (sizeof(A) / sizeof(A[0]))

Questo è un molto popolare e spesso utilizzato in macro.Questo è molto utile quando, ad esempio, è necessario scorrere un array.

int main(void)
{
    int a[] = {1, 2, 3, 4, 5};
    int i;
    for (i = 0; i < ARRAY_LENGTH(a); ++i) {
        printf("a[%d] = %d\n", i, a[i]);
    }
    return 0;
}

Qui non importa se un altro programmatore aggiunge cinque elementi a nel decleration.Il for-ciclo sempre iterare su tutti gli elementi.

La libreria C funzioni di confrontare la memoria e le stringhe sono molto brutti da utilizzare.

Tu scrivi:

char *str = "Hello, world!";

if (strcmp(str, "Hello, world!") == 0) {
    /* ... */
}

o

char *str = "Hello, world!";

if (!strcmp(str, "Hello, world!")) {
    /* ... */
}

Per verificare se str punti di "Hello, world".Io personalmente penso che entrambe queste soluzioni sembrano abbastanza brutto e confuso (soprattutto !strcmp(...)).

Qui ci sono due neat macro di alcune persone (tra cui io) quando dovranno confrontare le stringhe o di memoria utilizzando strcmp/memcmp:

/* Compare strings */
#define STRCMP(A, o, B) (strcmp((A), (B)) o 0)

/* Compare memory */
#define MEMCMP(A, o, B) (memcmp((A), (B)) o 0)

Ora potete scrivere il codice come questo:

char *str = "Hello, world!";

if (STRCMP(str, ==, "Hello, world!")) {
    /* ... */
}

Qui è l'intenzione molto più chiara!

Questi sono i casi erano le macro vengono utilizzati per operazioni di funzioni in grado di raggiungere.Le macro non dovrebbe essere utilizzato per sostituire le funzioni ma hanno altri usi bene.

Dato che i commenti, la tua domanda, non si può non apprezzare è che la chiamata di una funzione, che possono comportare una discreta quantità di overhead.I parametri e la chiave di registro può essere copiato lo stack e stack svolto sulla via d'uscita.Questo era particolarmente vero per i vecchi chip Intel.Macro lasciare che il programmatore mantenere l'astrazione di una funzione (o quasi), ma è riuscito ad evitare l'overhead di costi di una chiamata di funzione.La parola chiave inline è consultivo, ma il compilatore non può sempre fare la cosa giusta.La gloria e il pericolo di 'C' è che di solito si può piegare il compilatore la tua volontà.

Nel tuo pane e burro, giorno-per-giorno di programmazione di applicazione di questo tipo di micro-ottimizzazione (evitando chiamate di funzione) è generalmente peggiore di inutile, ma se si sta scrivendo un tempo critico, funzione chiamata dal kernel di un sistema operativo, quindi si può fare una differenza enorme.

A differenza delle normali funzioni, si può fare del flusso di controllo (if, while, for,...) per le macro.Ecco un esempio:

#include <stdio.h>

#define Loop(i,x) for(i=0; i<x; i++)

int main(int argc, char *argv[])
{
    int i;
    int x = 5;
    Loop(i, x)
    {
        printf("%d", i); // Output: 01234
    } 
    return 0;
} 

E ' bene per l'inline di codice e di evitare la chiamata di funzione di sovraccarico.E se si desidera modificare il comportamento in un secondo momento senza la modifica di un sacco di posti.Non è utile per le cose complesse, ma per semplici righe di codice che si desidera in linea, non è male.

Sfruttando il preprocessore C è la manipolazione del testo, si può costruire la C, l'equivalente di un polimorfici struttura di dati.Con questa tecnica siamo in grado di costruire un affidabile toolbox di primitive strutture di dati che può essere utilizzato in qualsiasi programma in C, in quanto sfruttano la sintassi del C e non le specifiche di una particolare implementazione.

Spiegazione dettagliata su come utilizzare la macro per la gestione dei dati di struttura è dato qui - http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html

Le macro consentono di sbarazzarsi di copia-incollati frammenti, che non è possibile eliminare in qualsiasi altro modo.

Per esempio (il vero codice, la sintassi di VS 2010 compilatore):

for each (auto entry in entries)
{
        sciter::value item;
        item.set_item("DisplayName",    entry.DisplayName);
        item.set_item("IsFolder",       entry.IsFolder);
        item.set_item("IconPath",       entry.IconPath);
        item.set_item("FilePath",       entry.FilePath);
        item.set_item("LocalName",      entry.LocalName);
        items.append(item);
    }

Questo è il luogo dove si passa il valore di un campo con lo stesso nome in un motore di script.È questo copia-incollato?Sì. DisplayName è usato come una stringa di una sceneggiatura e di un nome di campo per il compilatore.Che è brutta?Sì.Se si il refactoring del codice e rinominare LocalName per RelativeFolderName (come ho fatto io) e si dimentica di fare lo stesso con la stringa (come ho fatto io), lo script funzionerà in un modo che non ti aspetti (in realtà, nel mio esempio, dipende hai dimenticato di rinominare il campo in un separato file di script, ma se lo script è utilizzato per la serializzazione, sarebbe un 100% bug).

Se si utilizza una macro per questo, non ci sarà spazio per il bug:

for each (auto entry in entries)
{
#define STR_VALUE(arg) #arg
#define SET_ITEM(field) item.set_item(STR_VALUE(field), entry.field)
        sciter::value item;
        SET_ITEM(DisplayName);
        SET_ITEM(IsFolder);
        SET_ITEM(IconPath);
        SET_ITEM(FilePath);
        SET_ITEM(LocalName);
#undef SET_ITEM
#undef STR_VALUE
        items.append(item);
    }

Purtroppo, questo apre una porta per altri tipi di bug.Si può fare un errore di battitura scrivendo la macro e non vedrà mai un viziato codice, perché il compilatore non mostrare come appare dopo tutto il pre-elaborazione.Qualcun altro potrebbe usare lo stesso nome (che è il motivo per cui ho "versione" macro al più presto #undef).Così, da utilizzare con saggezza.Se vedi un altro modo di sbarazzarsi di copia-incollato il codice (come funzioni), utilizzare in quel modo.Se vedi che sbarazzarsi di copia-incollato il codice con le macro non vale il risultato, mantenere il copia-incollato il codice.

Una delle ovvie ragioni è che utilizzando una macro, il codice sarà ampliato in fase di compilazione, e si ottiene una pseudo-funzione di chiamata senza la chiamata di sovraccarico.

In caso contrario, si può anche utilizzare per costanti simboliche, in modo che non devi modificare lo stesso valore in diversi luoghi di cambiare una piccola cosa.

Macro ..per quando &#(*$& compilatore semplicemente si rifiuta di inline qualcosa.

Che dovrebbe essere un poster motivazionali, no?

In tutta serietà, google il preprocessore abuso (si può vedere una simile domanda #1 il risultato).Se sto scrivendo una macro che va oltre la funzionalità di assert(), io di solito cerco di vedere se il mio compilatore sarebbe in realtà inline una funzione simile.

Altri sostengono contro l'uso di #se per la compilazione condizionale ..piuttosto è:

if (RUNNING_ON_VALGRIND)

piuttosto che

#if RUNNING_ON_VALGRIND

..per scopi di debug, in quanto si può vedere il se (), ma non #se in un debugger.Poi ci tuffiamo in #ifdef vs #se.

Se i suoi in 10 righe di codice, provare a in linea esso.Se non possono essere sostituite, cercare di ottimizzare.Se è troppo stupido per essere una funzione, fare una macro.

Mentre io non sono un grande fan di macro e non tendono a scrivere molto C è più, in base alla mia attuale tasking, qualcosa di simile a questo (che, ovviamente, potrebbe avere alcuni effetti collaterali) è conveniente:

#define MIN(X, Y)  ((X) < (Y) ? (X) : (Y))

Ora non ho scritto nulla di simile in anni, ma 'funzioni' come che sono stati tutto il codice che ho mantenuto più presto nella mia carriera.Credo che l'espansione potrebbe essere considerato conveniente.

Non ho visto nessuno di menzionare questo modo, per quanto riguarda la funzione macro, ad esempio:

#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))

Generalmente si consiglia di evitare l'uso di macro quando non necessario, per molte ragioni, la leggibilità essere la preoccupazione principale.Così:

Quando si dovrebbe utilizzare queste, in funzione?

Quasi mai, poiché non c'è più leggibile alternativa inline, vedere https://www.greenend.org.uk/rjk/tech/inline.html o http://www.cplusplus.com/articles/2LywvCM9/ (il secondo link è una pagina C++, ma il punto è applicabile ai compilatori c per quanto ne so).

Ora, la piccola differenza è che le macro sono gestiti dal pre-processore e inline è gestita dal compilatore, ma non c'è alcuna differenza pratica al giorno d'oggi.

quando è opportuno utilizzare questi?

Per le piccole funzioni (due o tre camicie max).L'obiettivo è quello di ottenere qualche vantaggio durante il tempo di esecuzione di un programma, come la funzione macro (e funzioni inline) codice sostituzioni fatto durante il pre-líelaborazione (o la compilazione in caso di inline) e non sono funzioni reali di vita, in memoria, in modo che non c'è alcuna funzione di chiamata di sovraccarico (maggiori dettagli nelle pagine collegate).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top