1

La plupart du code que j'ai lu utilise un int pour la gestion des erreurs standard (valeurs de retour de fonctions et autres). Mais je me demande s'il est utile d'utiliser un uint_8 compilateur - lire: la plupart des compilateurs C sur la plupart des architectures - produire des instructions en utilisant le mode d'adresse immédiate - à savoir, intégrer l'entier de 1 octet dans le instruction ? L'instruction clé à laquelle je pense est la comparaison après une fonction, en utilisant uint_8 comme type de retour, retourne. Je pourrais penser à des choses incorrectement, comme l'introduction d'un type de 1 byte cause juste des problèmes d'alignement - il y a probablement une raison parfaitement saine de compiler les choses en 4 octets et c'est probablement la raison pour laquelle tout le monde utilise ints - et puisque c'est un problème lié à la pile plutôt qu'au tas, il n'y a pas de frais généraux.Programmation C et efficacité de la variable error_code

Faire la bonne chose est ce à quoi je pense. Mais disons que pour des raisons d'argument c'est un microprocesseur populaire et bon marché pour une montre intelligente et qu'il est configuré avec 1k de mémoire mais possède différents modes d'adressage dans son jeu d'instructions: D

Une autre question pour spécialiser la discussion (x86) serait la suivante: est le littéral:

uint_32 x=func(); x==1; 

et

uint_8 x=func(); x==1; 

du même type? ou le compilateur va-t-il générer un littéral de 8 octets dans le second cas. Si c'est le cas, il peut l'utiliser pour générer une instruction de comparaison qui a le littéral comme valeur immédiate et l'int retourné comme référence de registre. See CMP instruction types..

Another Refference for the x86 Instruction Set.

+2

Je pense que vous vous inquiétez de ce que l'on appelle habituellement «micro-optimisation». Avez-vous un contexte particulier? –

Répondre

4

Voici ce qu'un compilateur particulier fera pour le code suivant:

extern int foo(void) ; 
void bar(void) 
{ 
     if(foo() == 31) { //error code 31 
       do_something(); 
     } else { 
       do_somehing_else(); 
     } 
} 

    0: 55      push %ebp 
    1: 89 e5     mov %esp,%ebp 
    3: 83 ec 08    sub $0x8,%esp 
    6: e8 fc ff ff ff   call 7 <bar+0x7> 
    b: 83 f8 1f    cmp $0x1f,%eax 
    e: 74 08     je  18 <bar+0x18> 
    10: c9      leave 
    11: e9 fc ff ff ff   jmp 12 <bar+0x12> 
    16: 89 f6     mov %esi,%esi 
    18: c9      leave 
    19: e9 fc ff ff ff   jmp 1a <bar+0x1a> 

une instruction 3 octets pour le cmp. si foo() renvoie un caractère, nous obtenons b: 3c 1f cmp $ 0x1f,% al

Si vous recherchez l'efficacité cependant. Ne supposez pas que comparer des choses en% a1 est plus rapide que de les comparer avec% eax

2

processeurs aime généralement travailler avec leurs tailles de registre naturel, qui est C « int ».

Bien qu'il y ait des exceptions, vous pensez trop à un problème qui n'existe pas.

+0

en effet.Je suis, mais si elle pouvait utiliser l'adressage immédiat, il ne serait pas un registre, non? –

+1

@Vainstah: C'est complètement inutile: les registres sont plus rapides, la séquence call/cmp/jz ne peut plus être optimisée, et si quelqu'un d'autre avait besoin de ce registre, vous auriez un changement de contexte. –

+0

Il y a deux éléments dans l'instruction , le littéral de comparaison codé en dur et la valeur renvoyée. La valeur de retour ne peut pas être codée en dur, mais la première peut l'être. Par exemple en x86 l'instruction CMP peut prendre ses deux opérandes comme REG et IMMED. si les deux éléments avec 4 octets ils devraient tous les deux être dans les registres. Mais le littéral codé en dur est de 8 octets, il peut être mis dans l'instruction. –

3

Il peut y avoir de très petites différences de vitesse entre les différents types intégraux sur une architecture particulière. Mais vous ne pouvez pas compter dessus, cela peut changer si vous passez à un matériel différent, et il peut même courir plus lentement si vous passez à un matériel plus récent.

Et si vous parlez de x86 dans l'exemple que vous donnez, vous faites une fausse supposition: Un immédiat doit être de type uint8_t.

En fait immediates 8 bits incorporés dans les instructions sont de type int8_t et peuvent être utilisés avec des octets, des mots, doubles mots et qwords, en notation C: char, short, int et long long.

Donc, sur cette architecture, il n'y aurait aucun avantage, ni la taille du code, ni la vitesse d'exécution.

+0

donc si un type de retour de 4 octets est utilisé partout, puis-je supposer que les architectes CPU et les auteurs de compilateurs sont d'accord sur ce point, pour mettre en dérision ma tête: D? –

+1

Je pense que c'est une sorte de co-évolution: C a évolué comme une première abstraction très efficace autour de l'architecture typciale von-neumann. Bientôt, les microprocesseurs ont commencé à implémenter efficacement les modes d'adressage C typiques. Donc, oui, les auteurs de compilateurs et les concepteurs de processeurs sont d'accord sur ce point ;-) – hirschhornsalz

+0

Fantastique: D Heureux Je n'ai jamais écrit de code avec des types de retour de 8 octets. Merci beaucoup drhirsch. –

3

Vous devez utiliser les types int int ou unsigned pour vos calculs. Utiliser des types plus petits uniquement pour les composés (structures/réseaux). La raison en est que int est normalement défini comme étant le type intégral "le plus naturel" pour le processeur, tout autre type dérivé peut nécessiter un traitement pour fonctionner correctement. Nous avions dans notre projet compilé avec gcc sur Solaris pour SPARC le cas qui accède à la variable 8 bits et 16 bits ajouté une instruction au code. Lors du chargement d'un type plus petit de la mémoire, il fallait s'assurer que la partie supérieure du registre était correctement définie (extension de signe pour le type signé ou 0 pour non signé).Cela a allongé le code et augmenté la pression sur les registres, ce qui a détérioré les autres optimisations.

J'ai un exemple concret:

Je déclarai deux variables d'une struct uint8_t et obtenu ce code dans Sparc Asm:

if(p->BQ > p->AQ) 

a été traduit en

ldub [%l1+165], %o5 ! <variable>.BQ, 
ldub [%l1+166], %g5 ! <variable>.AQ, 
and  %o5, 0xff, %g4 ! <variable>.BQ, <variable>.BQ 
and  %g5, 0xff, %l0 ! <variable>.AQ, <variable>.AQ 
cmp  %g4, %l0 ! <variable>.BQ, <variable>.AQ 
bleu,a,pt %icc, .LL586 ! 

Et voici ce que j'ai eu quand j'ai déclaré les deux variables comme uint_t

lduw [%l1+168], %g1 ! <variable>.BQ, 
lduw [%l1+172], %g4 ! <variable>.AQ, 
cmp  %g1, %g4 ! <variable>.BQ, <variable>.AQ 
bleu,a,pt %icc, .LL587 ! 

Deux opérations arithmétiques moins et 2 registres plus pour d'autres choses