IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

C++17 crée une utilisation pratique de l'opérateur d'indexation de tableau rétrograde
Par Raymond Chen, Senior Software Engineer chez Microsoft

Le , par Raymond Chen

86PARTAGES

6  0 
Il est bien connu que si a est un pointeur ou un tableau et i un entier, alors a[i] et i[a] sont équivalents en C et C++, ce qui donne lieu à des hilarités telles que :

Code : Sélectionner tout
1
2
3
4
5
6
7
void haha() 
{ 
    int a[5]; 
    for (i = 0; i < 5; i++) { 
        i[a] = 42; 
    } 
}
Il y a très peu d'utilisation pratique pour cette équivalence, à part pour faire des farces aux gens¹.

Et puis C++17 est arrivé.

L'un des changements apportés au langage de base en C++17 a été le renforcement de l'ordre des règles d'évaluation, formellement connu sous le nom de séquençage. Nous avions déjà rencontré ce problème en étudiant un crash qui semblait concerner une opération std::move.

L'une des opérations qui a reçu un ordre d'évaluation défini est l'opérateur d'indice. À partir de C++17, a[b] évalue toujours a avant d'évaluer b.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int* p; 
int index(); 
 
auto test() 
{ 
    return p[index()]; 
} 
 
// Compiled as C++14 
 
    sub     rsp, 40 
    call    index       ; call index first 
    movsxd  rcx, rax 
    mov     rax, p      ; then fetch p 
    mov     eax, [rax + rcx * 4] 
    add     rsp, 40 
    ret 
 
// Compiled as c++17 
 
    push    rbx 
    sub     rsp, 32 
    mov     rbx, p      ; fetch p first 
    call    index       ; then call index 
    movsxd  rcx, rax 
    mov     eax, [rbx + rcx * 4] 
    add     rsp, 32 
    pop     rbx 
    ret
Par conséquent, si votre évaluation de l'index peut avoir un effet secondaire sur l'évaluation du pointeur, vous pouvez inverser l'ordre pour forcer l'index à être calculé en premier.

Code : Sélectionner tout
1
2
3
4
auto test() 
{ 
    return index()[p]; 
}
Étonnez vos amis ! Déconcertez vos ennemis !

Bavardage en prime : clang implémente cela correctement, mais msvc (v19) et gcc (v13) se trompent d'ordre et chargent p avant d'appeler index. (Par comparaison, icc se trompe aussi, mais dans l'autre sens : Il charge toujours p en dernier).

¹ Une autre utilisation pratique est de contourner toute surcharge possible de l'opérateur [], comme indiqué dans le chapitre 14 de Imperfect C++ :

Code : Sélectionner tout
#define ARRAYSIZE(a) (sizeof(a) / sizeof(0[a]))
En inversant l'ordre dans 0[a], cela permet de contourner une éventuelle surcharge de a[].

Code : Sélectionner tout
1
2
std::vector<int> v(5); 
int size = ARRAYSIZE(v); // compiler error
Cependant, cette méthode n'est pas infaillible. Il suffit de créer un imbécile plus astucieux : Si v est un pointeur ou un objet convertible en pointeur, alors ce pointeur ira volontiers à l'intérieur de 0[...].

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
struct Funny 
{ 
    operator int*() { return oops; } 
    int oops[5]; 
    int extra; 
}; 
 
Funny f; 
int size1 = ARRAYSIZE(f); // oops: 6 
 
int* p = f; 
int size2 = ARRAYSIZE(p); // oops: 1
Heureusement, vous n'avez pas besoin d'astuces macro. Vous pouvez laisser les fonctions constexpr du C++ faire le travail à votre place :

Code : Sélectionner tout
1
2
template<typename T, std::size_t N> 
constexpr std::size_t array_size(T(&)[N]) { return N; }

Et vous ?

Qu'en pensez-vous ?

Voir aussi :

Les travaux sur la norme C++ 23 sont terminés et cette nouvelle version porte le nom de code "Pandemic Edition", C++ 23 cherche à améliorer la vitesse de compilation et l'hygiène du code

Zig, présenté comme une alternative moderne au C, fait son apparition dans le top 50 de l'indice Tiobe des langages les plus populaires. Carbon, l'alternative C++ de Google, n'est classé que 168e

C++ vs Rust : une comparaison pratique de la vitesse de compilation et de test des deux langages de programmation, par Matthew Glazar, ingénieur en génie logiciel

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de grunk
Modérateur https://www.developpez.com
Le 05/06/2023 à 17:35
Qu'en pensez-vous ?
Que c'est exactement ces trucs qui font que ca me manque pas du tout de plus faire de C++ dans mon nouveau job
4  0 
Avatar de foetus
Expert éminent sénior https://www.developpez.com
Le 07/06/2023 à 12:38
Citation Envoyé par jmv  Voir le message
Alors ça, je ne savais pas ! depuis quand ?

Effectivement c'est la question depuis quand ?

Mais je pense que c'est 1 peu 1 fausse question
C'est plus le compilateur qui convertit a[i] en *(a + i) … et donc c'est interchangeable *(i + a).
Après il faudrait 1 expert gcc/ LLVM pour me dire si j'ai tord
4  0 
Avatar de dalfab
Expert éminent https://www.developpez.com
Le 07/06/2023 à 19:30
L'équivalence a[i] === *(a+i) === i[a] est la base du C, existe donc depuis le début.
Il existe un petit écart en C++, le résultat de a[i] est une x-value au lieu d'une l-value si l'expression a n'est pas une l-value, donc a[i] n'est pas totalement équivalent à i[a].

Ce qui change c'est que désormais l'ordre d'exécution est garanti, du moins depuis le C++17, je ne sais pas pour le C.
Donc pour fct1()[ fct2() ], on sait quelle fonction est appelée en premier, c'est fct1(). Mais par exemple pour fct1() = fct2();, on commence par fct2(). Ça me parait complexe de se souvenir de ça!

De plus, cette "astuce" me parait doublement contreproductive en C++, pour 2 raisons:
- on ne devrait pas utiliser l'arithmétique des pointeurs en C++. Il est préférable d'utiliser les objets faits pour cela.
- si on veut une chronologie précise. Il est bien plus lisible et sûr de décomposer l'expression:
Code : Sélectionner tout
1
2
3
auto&&  x = fct1(); 
auto&&  y = fct2();  // ou dans l'ordre inverse 
auto  z = x[ y ];
Les analyseurs de code doivent systématiquement tagger une ligne auto z = fct(1)[ fct2() ] comme étant dangereuse.
3  0 
Avatar de Bousk
Rédacteur/Modérateur https://www.developpez.com
Le 06/06/2023 à 21:33
Citation Envoyé par Raymond Chen Voir le message

Et vous ?

Qu'en pensez-vous ?
Gadget, et je vois pas comment quiconque de sencé pourrait écrire un tel code ou le laisser passer en revue.
2  0 
Avatar de d_d_v
Membre éprouvé https://www.developpez.com
Le 06/06/2023 à 15:51
Je ne suis pas sûr de bien comprendre.

Code : Sélectionner tout
return index()[p];
Va être une écriture valide ? C'est pour faciliter la lisibilité du code, c'est ça ?

Sinon, les décideurs derrière la norme C++ ne pourraient pas faire des trucs utiles pour la vie de tous les jours, pour faciliter l'écriture et la lisibilité ?
2  1 
Avatar de jmv
Membre confirmé https://www.developpez.com
Le 07/06/2023 à 13:13
Effectivement, ça parait logique.
Donc la réponse à ma question initiale serait : depuis qu'il existe un compilateur C.
1  0 
Avatar de jmv
Membre confirmé https://www.developpez.com
Le 07/06/2023 à 12:27
Citation Envoyé par grunk  Voir le message
Que c'est exactement ces trucs qui font que ca me manque pas du tout de plus faire de C++ dans mon nouveau job

+1
Il est bien connu que si a est un pointeur ou un tableau et i un entier, alors a[i] et i[a] sont équivalents en C et C++

Alors ça, je ne savais pas ! depuis quand ?
0  0