Comment créer un «espaceur» dans une structure de mémoire de classe C ++?


94

Le problème

Dans un contexte embarqué bare-metal de bas niveau , je voudrais créer un espace vide dans la mémoire, dans une structure C ++ et sans aucun nom, pour interdire à l'utilisateur d'accéder à un tel emplacement mémoire.

Pour le moment, je l'ai réalisé en mettant un vilain uint32_t :96;champ de bits qui remplacera commodément trois mots, mais cela soulèvera un avertissement de GCC (Bitfield trop grand pour tenir dans uint32_t), ce qui est assez légitime.

Bien que cela fonctionne bien, ce n'est pas très propre lorsque vous souhaitez distribuer une bibliothèque avec plusieurs centaines de ces avertissements ...

Comment faire ça correctement?

Pourquoi y a-t-il un problème en premier lieu?

Le projet sur lequel je travaille consiste à définir la structure mémoire des différents périphériques de toute une ligne de microcontrôleurs (STMicroelectronics STM32). Pour ce faire, le résultat est une classe qui contient une union de plusieurs structures qui définissent tous les registres, en fonction du microcontrôleur ciblé.

Voici un exemple simple de périphérique assez simple: une entrée / sortie à usage général (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;
    };
};

Où tout GPIO_MAPx_YYYest une macro, définie soit comme uint32_t :32soit le type de registre (une structure dédiée).

Ici, vous voyez uint32_t :192;qui fonctionne bien, mais cela déclenche un avertissement.

Ce que j'ai considéré jusqu'à présent:

J'aurais pu le remplacer par plusieurs uint32_t :32;(6 ici), mais j'ai quelques cas extrêmes où j'ai uint32_t :1344;(42) (entre autres). Je préférerais donc ne pas ajouter une centaine de lignes sur 8k autres, même si la génération de structure est scriptée.

Le message d'avertissement exact est quelque chose comme: width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type(J'adore à quel point c'est louche).

Je préfère ne pas résoudre ce problème en supprimant simplement l'avertissement, mais en utilisant

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

peut être une solution ... si je trouve TheRightFlag. Cependant, comme indiqué dans ce fil , gcc/cp/class.cavec cette triste partie de code:

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

Ce qui nous indique qu'il n'y a pas de -Wxxxdrapeau pour supprimer cet avertissement ...


26
avez-vous envisagé char unused[12];et ainsi de suite?
MM

3
Je supprimerais simplement l'avertissement. [class.bit] / 1 garantit le comportement de uint32_t :192;.
NathanOliver

3
@NathanOliver Je serais ravi aussi, mais il semble que cet avertissement ne soit pas supprimable (Utilisation de GCC) ou je n'ai pas trouvé comment le faire. De plus, ce n'est toujours pas une manière propre de le faire (mais ce serait plutôt satisfaisant). J'ai réussi à trouver le bon drapeau "-W" mais je n'ai pas réussi à l'appliquer uniquement sur mes propres fichiers (je ne veux pas que l'utilisateur supprime ce genre d'avertissements pour son travail)
J Faucher

3
BTW, vous pouvez écrire à la :42*32place de:1344
MM

1
Essayez ceci pour supprimer les avertissements? gcc.gnu.org/onlinedocs/gcc/…
Hitobat

Réponses:


36

Utilisez plusieurs champs de bits anonymes adjacents. Donc au lieu de:

    uint32_t :160;

par exemple, vous auriez:

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

Un pour chaque registre dont vous voulez être anonyme.

Si vous avez de grands espaces à remplir, il peut être plus clair et moins sujet aux erreurs d'utiliser des macros pour répéter l'espace unique de 32 bits. Par exemple, étant donné:

#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)

Ensuite, un espace de 1344 (42 * 32 bits) peut être ajouté ainsi:

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

Merci d'avoir répondu. J'ai déjà considéré cela, mais cela ajouterait plus de 200 lignes sur certains de mes fichiers ( uint32_t :1344;est à la place) donc je préférerais ne pas avoir à suivre cette voie ...
J Faucher

1
@JFaucher Ajout d'une solution possible à votre exigence de nombre de lignes. Si vous avez de telles exigences, vous pouvez les mentionner dans la question pour éviter d'obtenir des réponses qui ne les satisfont pas.
Clifford

Merci pour la modification et désolé de ne pas avoir indiqué le nombre de lignes. Mon point est que mon code est déjà pénible à plonger car il y a beaucoup de lignes et je préfère éviter d'en ajouter trop. Par conséquent, je demandais si quelqu'un connaissait un moyen "propre" ou "officiel" d'éviter d'utiliser un champ de bits anonyme adjacent (même si cela fonctionne bien). L'approche macro me semble bien cependant. Au fait, dans votre exemple, n'avez-vous pas un espace de 36 * 32 bits?
J Faucher

@JFaucher - corrigé. Les fichiers de mappage de registre d'E / S sont nécessairement volumineux en raison du grand nombre de registres - normalement, vous écrivez une seule fois et la maintenance n'est pas un problème car le matériel est une constante. Sauf en "masquant" les registres, vous effectuez des travaux de maintenance pour vous-même si vous devez y accéder ultérieurement. Vous savez bien sûr que tous les appareils STM32 ont déjà un en-tête de carte de registre fourni par le fournisseur? Il serait beaucoup moins sujet aux erreurs d'utiliser cela.
Clifford

2
Je suis d'accord avec vous et, pour être honnête, je pense que je vais suivre l'une de ces deux méthodes présentées dans votre réponse. Je voulais juste être sûr que C ++ ne fournit pas une meilleure solution avant de le faire. Je suis bien conscient que ST fournit ces en-têtes, mais ceux-ci sont basés sur l'utilisation massive de macros et d'opérations au niveau du bit. Mon projet est de construire un équivalent C ++ à ces en-têtes qui seront moins sujets aux erreurs (en utilisant des classes enum, des champs de bits, etc.). C'est pourquoi nous utilisons un script pour "traduire" les en-têtes CMSIS dans nos structures C ++ (et avons trouvé des erreurs dans les fichiers ST btw)
J Faucher

45

Que diriez-vous d'une manière C ++ - ish?

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;
}

Vous obtenez une saisie semi-automatique à cause de l' GPIOespace de noms et il n'y a pas besoin de remplissage factice. Même, ce qui se passe est plus clair, car vous pouvez voir l'adresse de chaque registre, vous n'avez pas du tout à vous fier au comportement de remplissage du compilateur.


1
Cela pourrait être moins bien optimisé qu'une structure pour accéder à plusieurs registres MMIO à partir de la même fonction. Avec un pointeur vers l'adresse de base dans un registre, le compilateur peut utiliser des instructions de chargement / stockage avec des déplacements immédiats, comme ldr r0, [r4, #16], tandis que les compilateurs sont plus susceptibles de manquer cette optimisation avec chaque adresse déclarée séparément. GCC chargera probablement chaque adresse GPIO dans un registre séparé. (À partir d'un pool littéral, bien que certains d'entre eux puissent être représentés comme des éléments immédiats pivotés dans l'encodage Thumb.)
Peter Cordes

4
Il s'avère que mes inquiétudes n'étaient pas fondées; ARM GCC optimise également de cette façon. godbolt.org/z/ztB7hi . Mais notez que vous voulez static volatile uint32_t &MAP0_MODER, non inline. Une inlinevariable ne se compile pas. ( staticévite d'avoir un stockage statique pour le pointeur, et volatilec'est exactement ce que vous voulez pour MMIO pour éviter l'élimination de la mémoire morte ou l'optimisation de l'écriture / lecture.)
Peter Cordes

1
@PeterCordes: les variables en ligne est une nouvelle fonctionnalité C ++ 17. Mais vous avez raison, staticfait tout aussi bien pour ce cas. Merci d'avoir mentionné volatile, je vais l'ajouter à ma réponse (et changer en ligne en statique, donc cela fonctionne pour le pré C ++ 17).
geza

2
Ce n'est pas un comportement strictement bien défini voir ce fil Twitter et peut-être que celui-ci est utile
Shafik Yaghmour

1
@JFaucher: créez autant d'espaces de noms que de structures que vous avez et utilisez des fonctions autonomes dans cet espace de noms. Donc, vous aurez GPIOA::togglePin().
geza

20

Dans l'arène des systèmes embarqués, vous pouvez modéliser le matériel en utilisant une structure ou en définissant des pointeurs vers les adresses de registre.

La modélisation par structure n'est pas recommandée car le compilateur est autorisé à ajouter un remplissage entre les membres à des fins d'alignement (bien que de nombreux compilateurs pour systèmes embarqués aient un pragma pour empaqueter la structure).

Exemple:

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);

Vous pouvez également utiliser la notation de tableau:

uint16_t status = UART1[UART_STATUS_OFFSET];  

Si vous devez utiliser la structure, à mon humble avis, la meilleure méthode pour ignorer les adresses serait de définir un membre et de ne pas y accéder:

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

Dans l'un de nos projets, nous avons à la fois des constantes et des structures de différents fournisseurs (le fournisseur 1 utilise des constantes tandis que le fournisseur 2 utilise des structures).


Merci pour votre réponse. Cependant, je choisis d'utiliser une approche de structure pour faciliter le travail de l'utilisateur lorsqu'il a obtenu une fonction de saisie semi-automatique (vous n'aurez que les bons attributs affichés) et je ne veux pas "montrer" à l'utilisateur les emplacements réservés comme souligné dans un commentaire de mon premier post.
J Faucher

Vous pouvez toujours avoir cela en faisant des adresses ci-dessus staticmembres d'une structure en supposant que la saisie semi-automatique est capable d'afficher les membres statiques. Sinon, il peut également s'agir de fonctions membres en ligne.
Phil1970

@JFaucher Je ne suis pas un spécialiste des systèmes embarqués et je n'ai pas testé cela, mais le problème de la saisie semi-automatique ne serait-il pas résolu en déclarant le membre réservé privé? (Vous pouvez déclarer des membres privés dans une structure, et vous pouvez utiliser public:et private:autant de fois que vous le souhaitez, pour obtenir le bon ordre des champs.)
Nathaniel

1
@Nathaniel: Pas si; si une classe est à la fois publicet les privatemembres de données non statiques, il n'est pas un type de mise en page standard , donc il ne fournit pas les garanties de commande que vous êtes la pensée de. (Et je suis presque sûr que le cas d'utilisation de l'OP nécessite un type de mise en page standard.)
ruakh

1
N'oubliez pas volatileces déclarations, BTW, pour les registres d'E / S mappés en mémoire.
Peter Cordes

13

geza a raison de ne pas vouloir utiliser de classes pour cela.

Mais, si vous insistez, la meilleure façon d'ajouter un membre inutilisé d'une largeur de n octets est simplement de le faire:

char unused[n];

Si vous ajoutez un pragma spécifique à l'implémentation pour empêcher l'ajout d'un remplissage arbitraire aux membres de la classe, cela peut fonctionner.


Pour GNU C / C ++ (gcc, clang et autres qui prennent en charge les mêmes extensions), l'un des emplacements valides pour placer l'attribut est:

#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");

(exemple sur l'explorateur du compilateur Godbolt montrant offsetof(GPIO, b)= 7 octets.)


9

Pour développer les réponses de @ Clifford et @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;
}

J'ai incorporé une variante de votre suggestion dans ma réponse suite à d'autres exigences dans un commentaire. Crédit là où le crédit est dû.
Clifford

7

Pour développer la réponse de Clifford, vous pouvez toujours supprimer les champs de bits anonymes.

Donc au lieu de

uint32_t :160;

utilisation

#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

Et puis utilisez-le comme

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

Malheureusement, vous aurez besoin d'autant de EMPTY_32_Xvariantes que d'octets que vous avez :( Pourtant, cela vous permet d'avoir des déclarations uniques dans votre structure.


5
En utilisant les macros Boost CPP, je pense que vous pouvez utiliser la récursivité pour éviter d'avoir à créer manuellement toutes les macros nécessaires.
Peter Cordes

3
Vous pouvez les mettre en cascade (jusqu'à la limite de récursivité du préprocesseur, mais c'est généralement suffisant). Untel #define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1et #define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1etc.
Miral

@PeterCordes peut-être, mais les balises indiquent que la compatibilité Booth C et C ++ est peut-être nécessaire.
Clifford

2
C et C ++ utilisent le même préprocesseur C; Je ne vois pas d'autre problème que de rendre disponible l'en-tête de boost nécessaire pour C. Ils mettent le truc CPP-macro dans un en-tête séparé.
Peter Cordes

1

Pour définir un grand espaceur sous forme de groupes de 32 bits.

#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

Je pense qu'il serait avantageux d'introduire une structure supplémentaire; ce qui peut, à son tour, résoudre le problème des entretoises.

Nommez les variantes

Bien que les espaces de noms plats soient agréables, le problème est que vous vous retrouvez avec une collection hétéroclite de champs et aucun moyen simple de passer tous les champs associés ensemble. De plus, en utilisant des structures anonymes dans une union anonyme, vous ne pouvez pas passer de références aux structures elles-mêmes, ni les utiliser comme paramètres de modèle.

Dans un premier temps, j'envisagerais donc de sortirstruct :

// 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 ...

Et enfin, l'en-tête global:

// Gpio.h
#pragma once

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

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

Maintenant, je peux écrire un void special_map0(Gpio:: Map0 volatile& map);, ainsi qu'obtenir un aperçu rapide de toutes les architectures disponibles en un coup d'œil.

Espaceurs simples

Avec la définition divisée en plusieurs en-têtes, les en-têtes sont individuellement beaucoup plus gérables.

Par conséquent, mon approche initiale pour répondre exactement à vos besoins serait de m'en tenir à des répétitions std::uint32_t:32;. Oui, cela ajoute quelques centaines de lignes aux lignes 8k existantes, mais comme chaque en-tête est individuellement plus petit, il peut ne pas être aussi mauvais.

Si vous êtes prêt à envisager des solutions plus exotiques, cependant ...

Présentation de $.

C'est un fait peu connu qui $est un caractère viable pour les identificateurs C ++; c'est même un personnage de départ viable (contrairement aux chiffres).

Une $apparition dans le code source soulèverait probablement des sourcils et $$$$attirera certainement l'attention lors des révisions de code. C'est quelque chose dont vous pouvez facilement profiter:

#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);
};

Vous pouvez même créer un simple «lint» comme hook de pré-validation ou dans votre CI qui recherche $$$$dans le code C ++ validé et rejeter de tels validations.


1
Souvenez-vous que le cas d'utilisation spécifique de l'OP est de décrire les registres d'E / S mappés en mémoire au compilateur. Il n'est jamais logique de copier la structure entière par valeur. (Et chaque membre comme l' GPIO_MAP0_MODERest vraisemblablement volatile.) Une référence ou une utilisation de paramètre de modèle des membres précédemment anonymes pourrait cependant être utile. Et pour le cas général des structures de remplissage, bien sûr. Mais le cas d'utilisation explique pourquoi l'OP les a laissés anonymes.
Peter Cordes

Vous pouvez utiliser $$$padding##Index_[N_];pour rendre le nom du champ plus explicite au cas où il apparaîtrait lors de la saisie semi-automatique ou lors du débogage. (Ou zz$$$paddingpour le faire trier après les GPIO...noms, parce que le but de cet exercice selon l'OP est plus agréable de compléter automatiquement les noms d'emplacement d'E / S mappés en mémoire.)
Peter Cordes

@PeterCordes: J'ai de nouveau scanné la réponse pour vérifier, et je n'ai jamais vu aucune mention de copie. J'ai cependant oublié le volatilequalificatif de la référence, qui a été corrigé. Quant à la dénomination; Je vais le laisser à l'OP. Il existe de nombreuses variantes (padding, réservé, ...), et même le "meilleur" préfixe pour l'auto-complétion peut dépendre de l'IDE à portée de main, bien que j'apprécie l'idée de peaufiner le tri.
Matthieu M.

Je faisais référence à " et à aucun moyen simple de passer tous les champs liés ensemble ", ce qui ressemble à une affectation de structure, et au reste de la phrase sur la dénomination des membres de la structure de l'union.
Peter Cordes

1
@PeterCordes: Je pensais passer par référence, comme illustré plus loin. Je trouve gênant que la structure de l'OP les empêche de créer des «modules» dont il est possible de prouver statiquement qu'ils n'accèdent qu'à une architecture spécifique (en prenant une référence au spécifique struct) et que les unionfinissent par se propager partout même dans des bits spécifiques à l'architecture qui se soucierait moins des autres.
Matthieu M.

0

Bien que je convienne que les structures ne doivent pas être utilisées pour l'accès au port d'E / S MCU, la question d'origine peut être répondue de cette manière:

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

Vous devrez peut-être remplacer __attribute__((packed))par #pragma packou similaire en fonction de la syntaxe de votre compilateur.

Le mélange de membres privés et publics dans une structure entraîne normalement que la disposition de la mémoire n'est plus garantie par la norme C ++. Cependant, si tous les membres non statiques d'une structure sont privés, il est toujours considéré comme une mise en page POD / standard, tout comme les structures qui les incorporent.

Pour une raison quelconque, gcc produit un avertissement si un membre d'une structure anonyme est privé, j'ai donc dû lui donner un nom. Alternativement, l'envelopper dans une autre structure anonyme supprime également l'avertissement (cela peut être un bogue).

Notez que le spacermembre n'est pas lui-même privé, donc les données sont toujours accessibles de cette façon:

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

Cependant, une telle expression ressemble à un piratage évident et, espérons-le, ne serait pas utilisée sans une très bonne raison, et encore moins comme une erreur.


1
Les utilisateurs ne peuvent pas déclarer d'identificateurs dans un espace de noms contenant des doubles traits de soulignement n'importe où dans le nom (en C ++, ou seulement au début en C); cela rend le code mal formé. Ces noms sont réservés à l'implémentation et peuvent donc en théorie entrer en conflit avec les vôtres de manière horriblement subtile et capricieuse. Quoi qu'il en soit, le compilateur n'a aucune obligation de conserver votre code s'il les contient. De tels noms ne sont pas un moyen rapide d'obtenir des noms «internes» pour votre propre usage.
underscore_d

Merci, réparé.
Jack White

-1

Anti-solution.

NE FAITES PAS CELA: Mélangez les champs privés et publics.

Peut-être qu'une macro avec un compteur pour générer des noms de variables uniqie sera utile?

#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
D'accord. Si personne ne me dérange, je laisserai la réponse comme quoi ne pas faire.
Robert Andrzejuk

4
@NicHartley Vu le nombre de réponses, on est proche d'une question "recherche". En recherche, la connaissance des impasses est encore la connaissance, elle évite aux autres de prendre le mauvais chemin. +1 pour la bravoure.
Oliv

1
@Oliv Et j'ai -1 parce que l'OP exigeait quelque chose, cette réponse a violé l'exigence, et c'est donc une mauvaise réponse. Je n'ai fait explicitement aucun jugement de valeur, positif ou négatif, sur la personne, dans aucun des deux commentaires - juste sur la réponse. Je pense que nous pouvons tous les deux convenir que c'est mauvais. Ce que cela dit de la personne est hors sujet pour ce site. (Bien que l'OMI, toute personne désireuse de prendre le temps de contribuer à une idée fasse quelque chose de bien, même si l'idée ne se concrétise pas)
Fund Monica's Lawsuit

2
Oui, c'est la mauvaise réponse. Mais je crains que certaines personnes ne parviennent à la même idée. À cause du commentaire et du lien, je viens d'apprendre quelque chose, ce ne sont pas des points négatifs pour moi.
Robert Andrzejuk
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.