1
votes

Passer un pointeur à placer dans un tableau

Apprendre le C et avoir des problèmes avec les pointeurs / tableaux. Utiliser MPLAB X et un PIC24FJ128GB204 comme périphérique cible, mais je ne pense pas vraiment que ce soit important pour cette question. La réponse peut être évidente, mais sans beaucoup de connaissances en C (encore), il est difficile de trouver une question similaire que je comprends suffisamment pour tirer des conclusions.

J'ai écrit une bibliothèque I2C avec la fonction suivante:

CMD_SingleShot[1]
&CMD_SingleShot[1]
(unsigned char)CMD_SingleShot + 1

Je veux appeler cette fonction depuis une autre, en utilisant un const prédéfini:

const unsigned char CMD_SingleShot [3] = {2, 0x2C, 0x06 };

Ceci a la longueur de la commande comme premier octet, puis les octets de commande.

La fonction appelante est:

int SHT31_GetData(unsigned char MeasurementData[]){
    // address device and start measurement
    if(I2C1_Write(SHT31_Address,
                0xFF,
                CMD_SingleShot[1],       // this is where the error message is given
                CMD_SingleShot[0])
                < 1){
        return -1;
    }
    //code omitted for brevity

    return 1;
}
  • lire des pointeurs et des tableaux et essayer de comprendre
  • recherche de fonctions similaires
  • a renoncé à comprendre et à essayer des choses aléatoires en espérant que les messages d'erreur me conduiraient à la bonne façon de le faire. Des choses comme:
int I2C1_Write(char DeviceAddress, unsigned char SubAddress, unsigned char *Payload, char ByteCnt){
    int PayloadByte = 0;
    int ReturnValue = 0;
    char SlaveWriteAddr;

    // send start
    if(I2C1_Start() < 0){
        I2C1_Bus_SetDirty;
        return I2C1_Err_CommunicationFail;
    }

    // address slave
    SlaveWriteAddr = (DeviceAddress << 1) & ~0x1;       // shift left, AND with 0xFE to keep bit0 clear
    ReturnValue = I2C1_WriteSingleByte(SlaveWriteAddr);
    switch (ReturnValue){
        case I2C1_OK:
            break;
        case I2C1_Err_NAK:
            I2C1_Stop();
            I2C1_Bus_SetDirty;
            return I2C1_Err_BadAddress;
        default:
            I2C1_Stop();
            I2C1_Bus_SetDirty;
            return I2C1_Err_CommunicationFail;

    }

    // part deleted for brevity

    // and finally payload
    for(PayloadByte = 0; PayloadByte < ByteCnt; PayloadByte++){
        // send byte one by one
        if(I2C1_WriteSingleByte(Payload[PayloadByte]) != I2C1_ACK){
            I2C1_Stop();
            I2C1_Bus_SetDirty;
            return I2C1_Err_CommunicationFail;            
        }
    }
    return I2C1_OK;
}   

cela vient de donner d'autres messages d'erreur.

Mes questions:

  • étant donné la fonction I2C1_Write telle quelle (en attendant un caractère non signé *) (par exemple, si ce n'était pas mon code et que je ne pouvais pas le changer), comment passerais-je le pointeur vers le deuxième octet du caractère non signé cont déployer? Je crois comprendre qu'un tableau est un pointeur, donc
  • puisqu'il s'agit de mon code, y a-t-il une meilleure façon de le faire?


8 commentaires

Que s'est-il passé lorsque vous avez essayé & CMD_SingleShot [1] ? Cela devrait au moins compiler.


Je peux le faire compiler, mais avec beaucoup d'erreurs et je n'ai pas encore les connaissances nécessaires pour juger lesquelles sont importantes, alors essayez de résoudre les problèmes. Message d'erreur lors de l'utilisation de & CMD_SingleShot [1]: Sensirion_SHT31.c: 40: 17: avertissement: la transmission de l'argument 3 de 'I2C1_Write' rejette les qualificatifs du type de cible de pointeur I2C1.h: 66: 5: note: attendu 'unsigned char *' mais argument est de type 'const unsigned char *'


Ce n'est pas une erreur, c'est un avertissement. Parce que vous avez déclaré que CMD_SingleShot contient des valeurs de const unsigned char . Mais la fonction attend un pointeur vers les valeurs de caractères non signés . Il semble que la fonction ne modifie pas la valeur de Payload , vous pouvez donc déclarer ce paramètre comme const unsigned char * Payload .


const unsigned char * MeasurementData [] , const unsigned char * Payload - mais la bibliothèque peut être cassée de toute façon, vous pouvez donc rejeter de force const là: (caractère non signé *) & CMD_SingleShot [1]


@kaylum c'est autant une erreur que l'autre.


@AnttiHaapala Vrai. J'ai filtré le fait que le premier n'a pas été signalé comme une erreur car il est clairement erroné. Alors que le deuxième cas pourrait être correct (mais pas de bonne pratique).


@kaylum ce sont tous les deux des violations de contraintes en C, ce qui signifie que le programme est invalide.


@kaylum Oubliez "ce n'est pas une erreur, c'est un avertissement". Chaque message que votre compilateur crache doit être considéré comme un bogue sévère jusqu'à preuve du contraire. Dans 99% des cas, le compilateur est correct et l'avertissement équivaut à un bogue. De plus, le langage C n'a pas le concept d'erreurs et d'avertissements, c'est une invention du compilateur. C n'a que des messages de diagnostic et un compilateur est censé donner un tel message chaque fois que le code viole la norme C. Il peut le faire avec un «avertissement», une «erreur» ou en livrant une lettre manuscrite personnelle à votre porte, à condition que vous en soyez averti.


3 Réponses :


2
votes

Premièrement, n'effectuez pas de casting à moins de savoir mieux que le compilateur ce qui se passe. Ce qui, dans votre cas, vous ne le faites pas. Il n'y a pas de quoi avoir honte.

Faire & CMD_SingleShot [1] est un pas dans la bonne direction. Le problème est que CMD_SingleShot [1] est de type const unsigned char et prend donc l'adresse de qui vous donne un pointeur de type const unsigned char * . Cela ne peut pas être passé au paramètre Payload car il attend un pointeur de unsigned char * . Heureusement, vous ne modifiez pas ce que pointe Payload , il n'y a donc aucune raison pour que cela soit non-const. Changez simplement la définition de Payload en const unsigned char * et le compilateur devrait être content.

Et au fait, en c, & Foo [n] est identique à Foo + n . Tout ce que vous écrivez est une question de goût.

Modifier:

Parfois, vous n'avez pas accès au code source de la bibliothèque, et en raison d'une mauvaise conception de la bibliothèque, vous êtes obligé de lancer un cast. Dans ce cas, c'est à vous de faire les choses correctement. Le compilateur se fera un plaisir de vous tirer une balle dans le pied si vous le lui demandez.

Dans votre cas, la distribution correcte serait (unsigned char *) & CMD_SingleShot [1] et NOT (unsigned char *) CMD_SingleShot [1] < / code>. Le premier cas interprète un pointeur d'un type comme un pointeur de type différent. Le second cas interprète un caractère non signé comme un pointeur, ce qui est très mauvais.


8 commentaires

Ça a du sens. Et en effet, il n'y a aucune raison de ne pas utiliser un caractère const unsigned * pour la charge utile. Mais disons que je n'avais pas accès au code de la bibliothèque I2C et que j'étais coincé avec une fonction attendant un caractère non signé *, comment l'appellerais-je alors? (caractère non signé *) CMD_SingleShot [1]?


Et pendant que je vous crois sur parole, pourquoi ne pas jeter? Dans le livre que j'ai utilisé pour apprendre le C, il est enseigné comme une bonne pratique (et cela entraîne également beaucoup moins de messages d'erreur).


@ DieterVansteenwegenON4DD Veuillez relire le premier paragraphe de la réponse de HAL9000. Même si votre livre prétend différemment, laissez-vous guider par notre expérience de plusieurs décennies de programmation C: les moulages sont principalement des signes de mauvaise conception, et parfois de pures erreurs. Certains livres sont tout simplement pires que d'autres.


@thebusybee Donc: en théorie, le casting n'est pas mauvais en tant que tel, mais doit être évité?


@ HAL9000 "Le deuxième cas interprète un caractère non signé comme un pointeur, ce qui est très mauvais." Parce que cela vous donnerait un pointeur vers l'adresse mémoire qui correspond à la valeur de l'adresse mémoire CMD_SingleShot [1] , correct?


@ DieterVansteenwegenON4DD Le c-standard ne spécifie pas ce que signifie (unsigned char *) CMD_SingleShot [1] . Mais sur un ordinateur typique, oui, ce serait très probablement un pointeur vers l'emplacement mémoire 0x2C et pas un pointeur vers l'emplacement mémoire qui contient la valeur 0x2C


@ DieterVansteenwegenON4DD Eh bien, c'est comme l'aspirine. Ce n'est pas mal en tant que tel et cela aide dans certains cas. Mais ces applications ont tendance à cacher la vraie raison du mal de tête, et c'est la même chose avec les moulages. Et si vous en abusez ou en utilisez trop, eh bien, vous pouvez imaginer le résultat. Vous écrivez le code source pas pour le compilateur, vous l'écrivez pour le prochain développeur qui le lira . Cela pourrait être votre futur moi. Cela m'est arrivé plus d'une fois. Rendez donc votre code source aussi propre que possible.


@ DieterVansteenwegenON4DD, Casts vous permet de faire des choses qui ne seraient pas possibles autrement. Mais ils ont un coût élevé, vous risquez de perdre les messages d'erreur et les avertissements du compilateur. Évitez-les autant que possible.



1
votes

Le passage de l'adresse du deuxième octet de votre commande se fait soit avec

CMD_SingleShot+1

soit

&CMD_SingleShot[1]

Mais alors vous tomberez sur une erreur de conversion invalide car votre commande est définie comme const unsigned char puis & CMD_SingleShot [1] est de type const unsigned char * mais votre fonction attend caractères non signés * .

Ce que vous pouvez faire est soit de changer l'argument de votre fonction:

int I2C1_Write (char DeviceAddress, unsigned char SubAddress, const unsigned char * Payload, char ByteCnt)

ou transtypez votre argument de passage:

I2C1_Write (SHT31_Address, 0xFF , (caractère non signé *) & CMD_SingleShot [1], CMD_SingleShot [0])

Dans ce dernier cas, sachez que rejeter la const'ness peut entraîner un comportement indéfini lors de sa modification par la suite.


6 commentaires

"Dans ce dernier cas, sachez que rejeter la const'ness peut entraîner un comportement indéfini lors de sa modification par la suite." Parce que la fonction appelée peut alors changer la variable?


De plus: "& CMD_SingleShot [1]". Pourquoi le & (adresse de)? Comme je l'ai compris, les tableaux sont des pointeurs, donc je m'attendrais à ce que CMD_SingleShot [1] fonctionne également ...


Oui - la fonction I2C_Write est capable de le changer car elle obtient un pointeur vers un caractère non signé non const, bien que les données source ne soient pas censées changer lors de la déclaration const. Alors je recommande d'éviter ça


@ DieterVansteenwegenON4DD CMD_SingleShot [1] n'est pas un pointeur, il est de type l'élément , c'est-à-dire const unsigned char .


CMD_SingleShot est un tableau et donc passé comme pointeur ( const unsigned char * ) mais CMD_SingleShot [1] n'est pas le tableau mais un élément unique de celui-ci ( const unsigned char ). Vous devez donc utiliser explicitement & pour obtenir l'adresse de cet élément particulier. Le premier élément est un cas particulier, car le pointeur de tableau est l'adresse du premier élément


@Odysseus ok, c'est logique. Tout en apprenant python, je pourrais faire du type (variable) pour obtenir le type, ce que je ne peux pas faire maintenant, ce qui rend beaucoup plus difficile de comprendre ce qui se passe ...



1
votes

L'appel de fonction est généralement correct, mais comme le 3ème paramètre de la fonction est un pointeur, vous devez passer une adresse à un tableau en conséquence, pas un seul caractère. Ainsi & CMD_SingleShot [1] plutôt que CMD_SingleShot[1 .

if(I2C1_Write(SHT31_Address,
                0xFF,
                &CMD_SingleShot[1], 
                CMD_SingleShot[0])
                < 1)

Cependant, lorsque vous faites cela, vous prétendez que vous obtenez des "rejets" qualifiers from pointer target "qui est une remarque sur l'exactitude de const - apparemment CMD_SingleShot est const (puisque c'est une variable flash ou quelque chose du genre?).

Cette erreur du compilateur à son tour signifie simplement que la fonction est mal conçue - une fonction d'écriture I2C ne doit clairement pas modifier les données, il suffit de les envoyer. La solution la plus correcte est donc de changer la fonction pour prendre const unsigned char * Payload . Etudiez const correctness - si une fonction ne modifie pas les données qui lui sont transmises par un pointeur, alors ce pointeur doit être déclaré comme const type * , "pointeur vers des données en lecture seule de type ".

S'il n'est pas possible de changer la fonction parce que vous êtes coincé avec une API écrite par quelqu'un d'autre, alors vous devrez copier les données dans un tampon de lecture / écriture avant en le passant à la fonction. "Casting away" const n'est presque jamais correct (bien que cela fonctionne le plus souvent dans la pratique, mais sans garanties).


Autres préoccupations:

  • Lors de la programmation du C en général, et du C intégré en particulier, vous devez utiliser stdint.h au lieu des types par défaut de C, qui posent problème car ils ont des tailles variables. p>

  • N'utilisez jamais char (sans unsigned ) pour autre chose que des chaînes réelles. Il a une signature définie par l'implémentation et est généralement dangereux - ne jamais l'utiliser pour stocker des données brutes.

  • Lors de la programmation d'un MCU 8 bits, utilisez toujours uint8_t / int8_t lorsque vous savez à l'avance que la variable ne contiendra pas de valeurs supérieures à 8 bits . Il existe de nombreux cas où le compilateur ne peut tout simplement pas optimiser les valeurs 16 bits jusqu'à 8 bits.

  • N'utilisez jamais d'opérandes signés ou potentiellement signés pour les opérateurs binaires. Un code tel que (DeviceAddress est extrêmement dangereux. Non seulement DeviceAddress est potentiellement signé et pourrait finir par être négatif, mais il est implicitement promu en int . De même, 0x1 est de type int et ainsi de suite le complément de 2 PIC ~ 0x1 se résume en fait à -2 qui n'est pas ce que vous voulez.

    Essayez plutôt de suffixer u toutes les constantes entières et d'étudier Règles de promotion de type implicite .


3 commentaires

Beaucoup d'informations là-bas, merci. 2 questions: 1. Donc, caractères non signés peut-il être utilisé pour les données brutes 8 bits? 2. stdint.h serait alors dépendant de l'architecture? 3. Je ne comprends pas complètement (mais je ne veux pas douter) votre dernier point: même si DeviceAddress serait signé, ne serait-il pas "simplement" un paquet de 8 bits qui seraient envoyés à le registre d'adresse I2C bit pour bit? Dans ce cas, est-ce vraiment important? Je vais lire sur les "opérandes signés". La dernière phrase et le dernier lien sont sans aucun doute importants, mais prendront du temps à digérer et à comprendre.


@ DieterVansteenwegenON4DD Oui, le caractère non signé est correct, mais int n'est pas correct car les types signés sont presque toujours dangereux dans Embedded. Ces types dépendent de l'architecture, alors que uint8_t est toujours le complément de 8 bits 2, que vous exécutiez le code sur un PIC ou un x86 64. Le problème avec l'arithmétique signée est que vous pouvez facilement obtenir un comportement inattendu. Prenons par exemple #define ALL_ONE 0xFF ... unsigned char peu importe = 0; ... if (~ what == ALL_ONE) , qui a un gros bug à cause de la promotion entière.


Hm, pourquoi ~ 0x1 se résume à -1 sur le complément de 2?