Come creo un "spaziatore" in una struttura di memoria di classe C ++?


94

Il problema

In un contesto embedded bare-metal di basso livello , vorrei creare uno spazio vuoto nella memoria, all'interno di una struttura C ++ e senza alcun nome, per impedire all'utente di accedere a tale posizione di memoria.

In questo momento, l'ho ottenuto inserendo un brutto uint32_t :96;bitfield che prenderà opportunamente il posto di tre parole, ma solleverà un avviso da GCC (Bitfield troppo grande per adattarsi a uint32_t), che è abbastanza legittimo.

Anche se funziona bene, non è molto pulito quando si desidera distribuire una libreria con diverse centinaia di questi avvisi ...

Come posso farlo correttamente?

Perché c'è un problema in primo luogo?

Il progetto a cui sto lavorando consiste nel definire la struttura della memoria di diverse periferiche di un'intera linea di microcontrollori (STMicroelectronics STM32). Per fare ciò, il risultato è una classe che contiene un'unione di diverse strutture che definiscono tutti i registri, a seconda del microcontrollore di destinazione.

Un semplice esempio per una periferica piuttosto semplice è il seguente: un General Purpose Input / Output (GPIO)

union
{

    struct
    {
        GPIO_MAP0_MODER;
        GPIO_MAP0_OTYPER;
        GPIO_MAP0_OSPEEDR;
        GPIO_MAP0_PUPDR;
        GPIO_MAP0_IDR;
        GPIO_MAP0_ODR;
        GPIO_MAP0_BSRR;
        GPIO_MAP0_LCKR;
        GPIO_MAP0_AFR;
        GPIO_MAP0_BRR;
        GPIO_MAP0_ASCR;
    };
    struct
    {
        GPIO_MAP1_CRL;
        GPIO_MAP1_CRH;
        GPIO_MAP1_IDR;
        GPIO_MAP1_ODR;
        GPIO_MAP1_BSRR;
        GPIO_MAP1_BRR;
        GPIO_MAP1_LCKR;
        uint32_t :32;
        GPIO_MAP1_AFRL;
        GPIO_MAP1_AFRH;
        uint32_t :64;
    };
    struct
    {
        uint32_t :192;
        GPIO_MAP2_BSRRL;
        GPIO_MAP2_BSRRH;
        uint32_t :160;
    };
};

Dove all GPIO_MAPx_YYYè una macro, definita come uint32_t :32o il tipo di registro (una struttura dedicata).

Qui vedi il uint32_t :192;che funziona bene, ma fa scattare un avviso.

Quello che ho considerato finora:

Potrei averlo sostituito con diversi uint32_t :32;(6 qui), ma ho alcuni casi estremi in cui houint32_t :1344; (42) (tra gli altri). Quindi preferirei non aggiungere un centinaio di righe in cima ad altre 8k, anche se la generazione della struttura è scriptata.

L'esatto messaggio di avvertimento è qualcosa del tipo: width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type(Adoro quanto sia ombroso).

Preferirei non risolverlo semplicemente rimuovendo l'avviso, ma l'uso di

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-WTheRightFlag"
/* My code */
#pragma GCC diagnostic pop

potrebbe essere una soluzione ... se trovo TheRightFlag. Tuttavia, come sottolineato in questo thread , gcc/cp/class.ccon questa triste parte di codice:

warning_at (DECL_SOURCE_LOCATION (field), 0,
        "width of %qD exceeds its type", field);

Il che ci dice che non esiste un -Wxxxflag per rimuovere questo avviso ...


26
hai pensato char unused[12];e così via?
MM

3
Vorrei solo sopprimere l'avvertimento. [class.bit] / 1 garantisce il comportamento di uint32_t :192;.
NathanOliver

3
@NathanOliver Lo farei volentieri anch'io, ma sembra che questo avviso non sia sopprimibile (utilizzando GCC) o non ho trovato come farlo. Inoltre, non è ancora un modo pulito di farlo (ma sarebbe piuttosto soddisfacente). Sono riuscito a trovare il flag "-W" corretto ma non sono riuscito ad applicarlo solo sui miei file (non voglio che l'utente rimuova questo tipo di avvertimenti per il suo lavoro)
J Faucher

3
A proposito, puoi scrivere al :42*32posto di:1344
MM

1
Prova questo per sopprimere gli avvisi? gcc.gnu.org/onlinedocs/gcc/…
Hitobat

Risposte:


36

Usa più bitfield anonimi adiacenti. Quindi invece di:

    uint32_t :160;

ad esempio, avresti:

    uint32_t :32;
    uint32_t :32;
    uint32_t :32;
    uint32_t :32;
    uint32_t :32;

Uno per ogni registro che vuoi rendere anonimo.

Se si dispone di ampi spazi da riempire, potrebbe essere più chiaro e meno soggetto a errori utilizzare le macro per ripetere il singolo spazio a 32 bit. Ad esempio, dato:

#define REPEAT_2(a) a a
#define REPEAT_4(a) REPEAT_2(a) REPEAT_2(a)
#define REPEAT_8(a) REPEAT_4(a) REPEAT_4(a)
#define REPEAT_16(a) REPEAT_8(a) REPEAT_8(a)
#define REPEAT_32(a) REPEAT_16(a) REPEAT_16(a)

Quindi uno spazio 1344 (42 * 32 bit) può essere aggiunto così:

struct
{
    ...
    REPEAT_32(uint32_t :32;) 
    REPEAT_8(uint32_t :32;) 
    REPEAT_2(uint32_t :32;)
    ...
};

Grazie per la risposta. Ho già considerato che, tuttavia, aggiungerebbe oltre 200 righe su alcuni dei miei file ( uint32_t :1344;è a posto) quindi preferirei non dover andare in questo modo ...
J Faucher

1
@JFaucher Aggiunta una possibile soluzione al requisito del conteggio delle righe. Se hai tali requisiti, potresti menzionarli nella domanda per evitare di ottenere risposte che non li soddisfano.
Clifford

Grazie per la modifica e ci scusiamo per non aver dichiarato il conteggio delle righe. Il punto è che il mio codice è già doloroso da immergere poiché ci sono molte righe e preferirei evitare di aggiungerne troppo. Pertanto, stavo chiedendo se qualcuno conosceva un modo "pulito" o "ufficiale" per evitare di utilizzare un bitfield anonimo adiacente (anche se funziona bene). L'approccio macro però mi sembra a posto. A proposito, nel tuo esempio, non hai uno spazio di 36 * 32 bit?
J Faucher

@JFaucher - corretto. I file di mappatura dei registri di I / O sono necessariamente di grandi dimensioni a causa dell'elevato numero di registri: normalmente si scrive una volta e la manutenzione non è un problema perché l'hardware è una costante. Tranne che "nascondendo" i registri, stai effettuando lavori di manutenzione per te stesso se in seguito avrai bisogno di accedervi. Sei consapevole, naturalmente, che tutti i dispositivi STM32 hanno già un'intestazione della mappa di registro fornita dal venditore? Sarebbe molto meno soggetto a errori usarlo.
Clifford

2
Sono d'accordo con te e, ad essere onesti, penso che seguirò uno di quei due metodi mostrati nella tua risposta. Volevo solo essere sicuro che C ++ non fornisse una soluzione migliore prima di farlo. Sono ben consapevole che ST fornisce queste intestazioni, tuttavia quelle si basano sull'uso massiccio di macro e operazioni bit per bit. Il mio progetto è costruire un C ++ equivalente a quelle intestazioni che saranno meno soggette a errori (usando classi enum, bitfield e così via). Ecco perché usiamo uno script per "tradurre" le intestazioni CMSIS nelle nostre strutture C ++ (e abbiamo trovato alcuni errori nei file ST tra l'altro)
J Faucher

45

Che ne dici di un modo in C ++?

namespace GPIO {

static volatile uint32_t &MAP0_MODER = *reinterpret_cast<uint32_t*>(0x4000);
static volatile uint32_t &MAP0_OTYPER = *reinterpret_cast<uint32_t*>(0x4004);

}

int main() {
    GPIO::MAP0_MODER = 42;
}

Ottieni il completamento automatico a causa dello GPIOspazio dei nomi e non è necessario un riempimento fittizio. Inoltre, è più chiaro cosa sta succedendo, poiché puoi vedere l'indirizzo di ogni registro, non devi fare affidamento sul comportamento di riempimento del compilatore.


1
Ciò potrebbe essere ottimizzato meno bene di una struttura per l'accesso a più registri MMIO dalla stessa funzione. Con un puntatore all'indirizzo di base in un registro, il compilatore può utilizzare istruzioni di caricamento / memorizzazione con spostamenti immediati, come ldr r0, [r4, #16], mentre è più probabile che i compilatori perdano tale ottimizzazione con ciascun indirizzo dichiarato separatamente. GCC caricherà probabilmente ogni indirizzo GPIO in un registro separato. (Da un pool letterale, sebbene alcuni di essi possano essere rappresentati come immediati ruotati nella codifica Thumb.)
Peter Cordes

4
È venuto fuori che le mie preoccupazioni erano infondate; Anche ARM GCC ottimizza in questo modo. godbolt.org/z/ztB7hi . Ma nota che vuoi static volatile uint32_t &MAP0_MODER, no inline. Una inlinevariabile non viene compilata. ( staticevita di avere una memoria statica per il puntatore ed volatileè esattamente ciò che vuoi per MMIO per evitare l'eliminazione del dead-store o l'ottimizzazione della scrittura / lettura.)
Peter Cordes

1
@PeterCordes: le variabili inline sono una nuova funzionalità di C ++ 17. Ma hai ragione, staticfa altrettanto bene per questo caso. Grazie per volatileaverlo menzionato , lo aggiungerò alla mia risposta (e cambierò inline in static, quindi funziona per pre C ++ 17).
geza

2
Questo non è un comportamento strettamente definito, vedi questo thread di Twitter e forse questo è utile
Shafik Yaghmour

1
@JFaucher: crea tanti spazi dei nomi quanti sono gli struct che hai e utilizza funzioni autonome in quello spazio dei nomi. Quindi, avrai GPIOA::togglePin().
geza

20

Nell'arena dei sistemi embedded, è possibile modellare l'hardware utilizzando una struttura o definendo i puntatori agli indirizzi di registro.

La modellazione in base alla struttura non è consigliata perché al compilatore è consentito aggiungere riempimento tra i membri per scopi di allineamento (sebbene molti compilatori per sistemi incorporati abbiano un pragma per comprimere la struttura).

Esempio:

uint16_t * const UART1 = (uint16_t *)(0x40000);
const unsigned int UART_STATUS_OFFSET = 1U;
const unsigned int UART_TRANSMIT_REGISTER = 2U;
uint16_t * const UART1_STATUS_REGISTER = (UART1 + UART_STATUS_OFFSET);
uint16_t * const UART1_TRANSMIT_REGISTER = (UART1 + UART_TRANSMIT_REGISTER);

Puoi anche usare la notazione dell'array:

uint16_t status = UART1[UART_STATUS_OFFSET];  

Se è necessario utilizzare la struttura, IMHO, il metodo migliore per saltare gli indirizzi sarebbe definire un membro e non accedervi:

struct UART1
{
  uint16_t status;
  uint16_t reserved1; // Transmit register
  uint16_t receive_register;
};

In uno dei nostri progetti abbiamo sia costanti che strutture di diversi fornitori (il fornitore 1 utilizza le costanti mentre il fornitore 2 utilizza le strutture).


Grazie per la tua risposta. Tuttavia, scelgo di utilizzare un approccio strutturale per facilitare il lavoro dell'utente quando ottiene una funzione di completamento automatico (verranno visualizzati solo gli attributi corretti) e non voglio "mostrare" all'utente gli slot riservati come sottolineato in un commento del mio primo post.
J Faucher

Puoi ancora averlo rendendo i staticmembri dell'indirizzo sopra indicati di una struttura assumendo che il completamento automatico sia in grado di mostrare membri statici. In caso contrario, possono essere anche funzioni membro inline.
Phil1970

@JFaucher Non sono una persona che si occupa di sistemi incorporati e non l'ho testato, ma il problema del completamento automatico non verrebbe risolto dichiarando privato il membro riservato? (Puoi dichiarare membri privati ​​in una struttura e puoi usare public:e private:tutte le volte che vuoi, per ottenere il corretto ordine dei campi.)
Nathaniel

1
@ Nathaniel: Non così; se una classe ha membri di dati sia publice privatenon statici, allora non è un tipo di layout standard , quindi non fornisce le garanzie di ordinamento a cui stai pensando. (E sono abbastanza sicuro che il caso d'uso dell'OP richiede un tipo di layout standard.)
ruakh

1
Non dimenticare volatilequelle dichiarazioni, BTW, per i registri I / O mappati in memoria.
Peter Cordes

13

geza ha ragione sul fatto che tu non voglia davvero usare le classi per questo.

Ma, se dovessi insistere, il modo migliore per aggiungere un membro inutilizzato di larghezza n byte, è semplicemente farlo:

char unused[n];

Se aggiungi un pragma specifico dell'implementazione per impedire l'aggiunta di spaziatura interna arbitraria ai membri della classe, questo può funzionare.


Per GNU C / C ++ (gcc, clang e altri che supportano le stesse estensioni), uno dei posti validi in cui inserire l'attributo è:

#include <stddef.h>
#include <stdint.h>
#include <assert.h>  // for C11 static_assert, so this is valid C as well as C++

struct __attribute__((packed)) GPIO {
    volatile uint32_t a;
    char unused[3];
    volatile uint32_t b;
};

static_assert(offsetof(struct GPIO, b) == 7, "wrong GPIO struct layout");

(esempio sull'esploratore del compilatore Godbolt che mostra offsetof(GPIO, b)= 7 byte.)


9

Per espandere le risposte di @ Clifford e @Adam Kotwasinski:

#define REP10(a)        a a a a a a a a a a
#define REP1034(a)      REP10(REP10(REP10(a))) REP10(a a a) a a a a

struct foo {
        int before;
        REP1034(unsigned int :32;)
        int after;
};
int main(void){
        struct foo bar;
        return 0;
}

Ho incorporato una variante del tuo suggerimento nella mia risposta a seguito di ulteriori requisiti in un commento. Credito a cui è dovuto il credito.
Clifford

7

Per espandere la risposta di Clifford, puoi sempre macro i bitfield anonimi.

Quindi invece di

uint32_t :160;

uso

#define EMPTY_32_1 \
 uint32_t :32
#define EMPTY_32_2 \
 uint32_t :32;     \ // I guess this also can be replaced with uint64_t :64
 uint32_t :32
#define EMPTY_32_3 \
 uint32_t :32;     \
 uint32_t :32;     \
 uint32_t :32
#define EMPTY_UINT32(N) EMPTY_32_ ## N

E poi usalo come

struct A {
  EMPTY_UINT32(3);
  /* which resolves to EMPTY_32_3, which then resolves to real declarations */
}

Sfortunatamente, avrai bisogno di tante EMPTY_32_Xvarianti quanti byte hai :( Tuttavia, ti permette di avere singole dichiarazioni nella tua struttura.


5
Usando le macro Boost CPP, penso che tu possa usare la ricorsione per evitare di dover creare manualmente tutte le macro necessarie.
Peter Cordes

3
Puoi metterli in cascata (fino al limite di ricorsione del preprocessore, ma di solito è ampio). Così #define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1e così #define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1via
Miral

@PeterCordes forse, ma i tag indicano che forse è necessaria la compatibilità tra C e C ++.
Clifford

2
C e C ++ utilizzano lo stesso preprocessore C; Non vedo un problema se non quello di rendere disponibile l'intestazione boost necessaria per C.Mettono le cose della macro CPP in un'intestazione separata.
Peter Cordes

1

Per definire un grande spaziatore come gruppi di 32 bit.

#define M_32(x)   M_2(M_16(x))
#define M_16(x)   M_2(M_8(x))
#define M_8(x)    M_2(M_4(x))
#define M_4(x)    M_2(M_2(x))
#define M_2(x)    x x

#define SPACER int : 32;

struct {
    M_32(SPACER) M_8(SPACER) M_4(SPACER)
};

1

Penso che sarebbe utile introdurre un po 'più di struttura; che può, a sua volta, risolvere il problema dei distanziatori.

Assegna un nome alle varianti

Mentre gli spazi dei nomi piatti sono belli, il problema è che si finisce con una raccolta eterogenea di campi e non un modo semplice per passare insieme tutti i campi correlati. Inoltre, utilizzando strutture anonime in un'unione anonima non è possibile passare riferimenti alle strutture stesse o utilizzarle come parametri del modello.

Come primo passo, quindi, prenderei in considerazione l' idea di suddividerestruct :

// GpioMap0.h
#pragma once

// #includes

namespace Gpio {
struct Map0 {
    GPIO_MAP0_MODER;
    GPIO_MAP0_OTYPER;
    GPIO_MAP0_OSPEEDR;
    GPIO_MAP0_PUPDR;
    GPIO_MAP0_IDR;
    GPIO_MAP0_ODR;
    GPIO_MAP0_BSRR;
    GPIO_MAP0_LCKR;
    GPIO_MAP0_AFR;
    GPIO_MAP0_BRR;
    GPIO_MAP0_ASCR;
};
} // namespace Gpio

// GpioMap1.h
#pragma once

// #includes

namespace Gpio {
struct Map1 {
    // fields
};
} // namespace Gpio

// ... others headers ...

E infine, l'intestazione globale:

// Gpio.h
#pragma once

#include "GpioMap0.h"
#include "GpioMap1.h"
// ... other headers ...

namespace Gpio {
union Gpio {
    Map0 map0;
    Map1 map1;
    // ... others ...
};
} // namespace Gpio

Ora posso scrivere un void special_map0(Gpio:: Map0 volatile& map);, oltre a ottenere una rapida panoramica di tutte le architetture disponibili a colpo d'occhio.

Distanziatori semplici

Con la definizione suddivisa in più intestazioni, le intestazioni sono individualmente molto più gestibili.

Pertanto, il mio approccio iniziale per soddisfare esattamente le tue esigenze sarebbe quello di continuare a ripetere std::uint32_t:32;. Sì, aggiunge alcune righe da 100 alle righe esistenti da 8.000, ma poiché ogni intestazione è individualmente più piccola, potrebbe non essere così male.

Se sei disposto a considerare soluzioni più esotiche, però ...

Presentazione di $.

È un fatto poco noto che $è un carattere valido per gli identificatori C ++; è anche un valido carattere iniziale (a differenza delle cifre).

Una $comparsa nel codice sorgente probabilmente solleverebbe le sopracciglia e $$$$attirerà sicuramente l'attenzione durante le revisioni del codice. Questo è qualcosa che puoi facilmente sfruttare:

#define GPIO_RESERVED(Index_, N_) std::uint32_t $$$$##Index_[N_];

struct Map3 {
    GPIO_RESERVED(0, 6);
    GPIO_MAP2_BSRRL;
    GPIO_MAP2_BSRRH;
    GPIO_RESERVED(1, 5);
};

Puoi anche mettere insieme un semplice "lint" come hook pre-commit o nel tuo CI che cerca $$$$nel codice C ++ sottoposto a commit e rifiutare tali commit.


1
Ricordare che il caso d'uso specifico dell'OP è per descrivere i registri di I / O mappati in memoria al compilatore. Non ha mai senso copiare l'intera struttura per valore. (E ogni membro come GPIO_MAP0_MODERè presumibilmente volatile.) Forse l'uso del riferimento o del parametro del modello dei membri precedentemente anonimi potrebbe essere utile, però. E per il caso generale di strutture di riempimento, certo. Ma il caso d'uso spiega perché l'OP li ha lasciati anonimi.
Peter Cordes

Potresti usare $$$padding##Index_[N_];per rendere il nome del campo più autoesplicativo nel caso in cui sia mai venuto fuori durante il completamento automatico o durante il debug. (O zz$$$paddingper ordinare in base ai GPIO...nomi, perché l'intero punto di questo esercizio secondo l'OP è un completamento automatico più carino per i nomi delle posizioni I / O mappati in memoria.)
Peter Cordes

@ PeterCordes: ho scansionato di nuovo la risposta per controllare e non ho mai visto alcun accenno alla copia. volatileTuttavia, ho dimenticato il qualificatore sul riferimento, che è stato corretto. Per quanto riguarda la denominazione; Lo lascio fare all'OP. Ci sono molte variazioni (riempimento, riservato, ...), e anche il prefisso "migliore" per l'auto-completamento può dipendere dall'IDE a portata di mano, anche se apprezzo l'idea di modificare l'ordinamento.
Matthieu M.

Mi riferivo a " e non è un modo semplice per passare tutti i campi correlati insieme ", che suona come assegnazione di strutture, e il resto della frase sulla denominazione dei membri della struttura dell'unione.
Peter Cordes

1
@ PeterCordes: stavo pensando di passare per riferimento, come illustrato più avanti. Trovo imbarazzante che la struttura dell'OP impedisca loro di creare "moduli" che possono essere staticamente provati per accedere solo a un'architettura specifica (facendo riferimento allo specifico struct) e che la struttura unionfinisca per essere propagata ovunque anche in bit specifici dell'architettura che potrebbe importare meno degli altri.
Matthieu M.

0

Sebbene sia d'accordo che le strutture non debbano essere utilizzate per l'accesso alla porta I / O MCU, la domanda originale può essere risolta in questo modo:

struct __attribute__((packed)) test {
       char member1;
       char member2;
       volatile struct __attribute__((packed))
       {
       private:
              volatile char spacer_bytes[7];
       }  spacer;
       char member3;
       char member4;
};

Potrebbe essere necessario sostituire __attribute__((packed))con #pragma packo simile a seconda della sintassi del compilatore.

Mescolare membri privati ​​e pubblici in una struttura normalmente risulta in quel layout di memoria non è più garantito dallo standard C ++. Tuttavia, se tutti i membri non statici di uno struct sono privati, è ancora considerato POD / layout standard, così come gli struct che li incorporano.

Per qualche motivo gcc produce un avviso se un membro di una struttura anonima è privato, quindi ho dovuto dargli un nome. In alternativa, inserendolo in un'altra struttura anonima si elimina anche l'avviso (questo potrebbe essere un bug).

Tieni presente che il spacermembro non è privato di per sé, quindi è ancora possibile accedere ai dati in questo modo:

(char*)(void*)&testobj.spacer;

Tuttavia, un'espressione del genere sembra un ovvio trucco e, si spera, non sarebbe stata utilizzata senza una buona ragione, figuriamoci come un errore.


1
Gli utenti non possono dichiarare identificatori in alcun namespace contenente doppi trattini bassi in qualsiasi punto del nome (in C ++ o solo all'inizio in C); così facendo il codice viene mal formato. Questi nomi sono riservati all'implementazione e quindi possono in teoria entrare in conflitto con i tuoi in modi orribilmente sottili e capricciosi. In ogni caso, il compilatore non ha l'obbligo di conservare il codice se lo contiene. Tali nomi non sono un modo rapido per ottenere nomi "interni" per uso personale.
underscore_d

Grazie, risolto.
Jack White

-1

Anti-soluzione.

NON FARE QUESTO: Mescola campi privati ​​e pubblici.

Forse sarà utile una macro con un contatore per generare nomi di variabili uniqie?

#define CONCAT_IMPL( x, y ) x##y
#define MACRO_CONCAT( x, y ) CONCAT_IMPL( x, y )
#define RESERVED MACRO_CONCAT(Reserved_var, __COUNTER__) 


struct {
    GPIO_MAP1_CRL;
    GPIO_MAP1_CRH;
    GPIO_MAP1_IDR;
    GPIO_MAP1_ODR;
    GPIO_MAP1_BSRR;
    GPIO_MAP1_BRR;
    GPIO_MAP1_LCKR;
private:
    char RESERVED[4];
public:
    GPIO_MAP1_AFRL;
    GPIO_MAP1_AFRH;
private:
    char RESERVED[8];
};


3
Ok. Se a nessuno importa, lascio la risposta come cosa non fare.
Robert Andrzejuk

4
@NicHartley Considerando il numero di risposte, siamo vicini a una domanda di "ricerca". Nella ricerca, la conoscenza delle impasse è ancora conoscenza, evita che gli altri prendano la strada sbagliata. +1 per il coraggio.
Oliv

1
@ Oliv And I -1'd perché l'OP richiedeva qualcosa, questa risposta ha violato il requisito, e quindi è una cattiva risposta. Non ho espressamente espresso alcun giudizio di valore, positivo o negativo, sulla persona, in nessuno dei due commenti - solo sulla risposta. Penso che possiamo concordare entrambi che è brutto. Ciò che dice sulla persona è fuori tema per questo sito. (Anche se, secondo me, chiunque sia disposto a dedicare un po 'di tempo a contribuire con un'idea sta facendo qualcosa di giusto, anche se l'idea non va a buon fine)
Finanzia la causa di Monica il

2
Sì, è la risposta sbagliata. Ma temo che alcune persone possano arrivare alla stessa idea. A causa del commento e del collegamento ho appena imparato qualcosa, non sono punti negativi per me.
Robert Andrzejuk
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.