Ceci est une ancienne révision du document !
Les attributs ci-dessous sont non standards et dépendent du compilateur.
__attribute__((const)) et __attribute__((pure))
Il est aussi possible d'utiliser [[gnu::pure]]
et [[gnu::const]]
.
Une fonction const
ne modifie aucune donnée et ne lit que ces arguments (pas de variable globale et pas de déréférencement de pointeur, y compris parmi ses arguments).
Une fonction pure
ne modifie aucune donnée accessible par d'autres fonctions (généralement appelées des observateurs). Elle peut accéder en lecture seule à des variables globales ou encore déréférencer des pointeurs passés en argument.
Les fonctions const
sont garanties idempotentes.
Les fonctions pure
peuvent ne pas renvoyer la même valeur avec les mêmes arguments. Une variable globale ou bien le contenu d'un tableau passé en pointeur peut changer. Cependant, le compilateur va optimiser en supposant l'application single-thread
. Donc si deux fonctions sont appelées avec les mêmes arguments et que le compilateur est sûr que les arguments ne sont pas modifiés (y compris le contenu des pointeurs), il ne va appeler la fonction qu'une seule fois et réutiliser la valeur retour.
The const attribute imposes greater restrictions on a function’s definition than the similar pure attribute.
The const attribute prohibits a function from reading objects that affect its return value between successive invocations.
In general, since a function cannot distinguish data that might change from data that cannot, const functions should never take pointer or, in C++, reference arguments.
Using the GNU Compiler Collection, Archive v9.2 le 06/03/2020
Rappel : l’attribut pure
n'est pas thread-safe
si la fonction fait appel à une variable globale ou si un de ces arguments peut être modifiée par un autre thread.
Ici, il faut bien activer l'option -O2
.
Code | Assembleur |
Deux appels consécutifs : optimisation.
- cpp/attribute/pure1.cpp
[[gnu::pure]] int square(int x);
int main(int argc, char* /*argv*/[])
{
int x = square(argc);
int y = square(argc);
return x + y;
}
|
- cpp/attribute/pure1.cpp.asm
0000000000000000 <main>:
0: endbr64
4: sub rsp,0x8
8: call d <main+0xd>
9: R_X86_64_PLT32 square(int)-0x4
d: add rsp,0x8
11: add eax,eax
13: ret
|
Modification entre deux appels : pas d'optimisation.
- cpp/attribute/pure2.cpp
[[gnu::pure]] int square(int x);
int main(int argc, char* /*argv*/[])
{
int x = square(argc);
argc++;
int y = square(argc);
return x + y;
}
|
- cpp/attribute/pure2.cpp.asm
0000000000000000 <main>:
0: endbr64
4: push rbp
5: push rbx
6: mov ebx,edi
8: sub rsp,0x8
c: call 11 <main+0x11>
d: R_X86_64_PLT32 square(int)-0x4
11: lea edi,[rbx+0x1]
14: mov ebp,eax
16: call 1b <main+0x1b>
17: R_X86_64_PLT32 square(int)-0x4
1b: add rsp,0x8
1f: add eax,ebp
21: pop rbx
22: pop rbp
23: ret
|
Utilisation intermédiaire par copie par une fonction : pas d'optimisation !!!
- cpp/attribute/pure3.cpp
[[gnu::pure]] int square(int x);
void f(int a);
int main(int argc, char* /*argv*/[])
{
int x = square(argc);
f(argc);
int y = square(argc);
return x + y;
}
|
- cpp/attribute/pure3.cpp.asm
0000000000000000 <main>:
0: endbr64
4: push rbp
5: mov ebp,edi
7: push rbx
8: sub rsp,0x8
c: call 11 <main+0x11>
d: R_X86_64_PLT32 square(int)-0x4
11: mov edi,ebp
13: mov ebx,eax
15: call 1a <main+0x1a>
16: R_X86_64_PLT32 f(int)-0x4
1a: mov edi,ebp
1c: call 21 <main+0x21>
1d: R_X86_64_PLT32 square(int)-0x4
21: add rsp,0x8
25: add eax,ebx
27: pop rbx
28: pop rbp
29: ret
|
Utilisation intermédiaire par copie par une fonction inconnue pure : optimisation.
- cpp/attribute/pure4.cpp
[[gnu::pure]] int square(int x);
[[gnu::pure]] int f(int a);
int main(int argc, char* /*argv*/[])
{
int x = square(argc);
x = x + f(argc);
int y = square(argc);
return x + y;
}
|
- cpp/attribute/pure4.cpp.asm
0000000000000000 <main>:
0: endbr64
4: push rbp
5: mov ebp,edi
7: push rbx
8: sub rsp,0x8
c: call 11 <main+0x11>
d: R_X86_64_PLT32 square(int)-0x4
11: mov edi,ebp
13: mov ebx,eax
15: call 1a <main+0x1a>
16: R_X86_64_PLT32 f(int)-0x4
1a: add rsp,0x8
1e: lea eax,[rax+rbx*2]
21: pop rbx
22: pop rbp
23: ret
|
Utilisation en lecture au milieu : optimisation.
- cpp/attribute/pure5.cpp
#include <cstdlib>
[[gnu::pure]] char square(char x);
int f(int a) { return rand() % a; }
int main(int argc, char* argv[])
{
char x = square(argv[0][0]);
argc = static_cast<unsigned char>(argv[0][0]);
char y = square(argv[0][0]);
return x + y + argc;
}
|
- cpp/attribute/pure5.cpp.asm
0000000000000000 <f(int)>:
0: endbr64
4: push rbx
5: mov ebx,edi
7: call c <f(int)+0xc>
8: R_X86_64_PLT32 rand-0x4
c: cdq
d: idiv ebx
f: pop rbx
10: mov eax,edx
12: ret
Disassembly of section .text.startup:
0000000000000000 <main>:
0: endbr64
4: mov rax,QWORD PTR [rsi]
7: push rbx
8: movsx edi,BYTE PTR [rax]
b: mov ebx,edi
d: call 12 <main+0x12>
e: R_X86_64_PLT32 square(char)-0x4
12: movzx ebx,bl
15: movsx eax,al
18: lea eax,[rbx+rax*2]
1b: pop rbx
1c: ret
|
Utilisation en écriture au milieu : pas d'optimisation.
- cpp/attribute/pure6.cpp
#include <cstdlib>
[[gnu::pure]] char square(char x);
int f(int a) { return rand() % a; }
int main(int argc, char* argv[])
{
char x = square(argv[0][0]);
argv[0][0] = x;
char y = square(argv[0][0]);
return x + y + argc;
}
|
- cpp/attribute/pure6.cpp.asm
0000000000000000 <f(int)>:
0: endbr64
4: push rbx
5: mov ebx,edi
7: call c <f(int)+0xc>
8: R_X86_64_PLT32 rand-0x4
c: cdq
d: idiv ebx
f: pop rbx
10: mov eax,edx
12: ret
Disassembly of section .text.startup:
0000000000000000 <main>:
0: endbr64
4: push r13
6: push r12
8: mov r12d,edi
b: push rbp
c: mov rbp,rsi
f: push rbx
10: sub rsp,0x8
14: mov r13,QWORD PTR [rsi]
17: movsx edi,BYTE PTR [r13+0x0]
1c: call 21 <main+0x21>
1d: R_X86_64_PLT32 square(char)-0x4
21: movsx ebx,al
24: mov BYTE PTR [r13+0x0],bl
28: mov rax,QWORD PTR [rbp+0x0]
2c: movsx edi,BYTE PTR [rax]
2f: call 34 <main+0x34>
30: R_X86_64_PLT32 square(char)-0x4
34: add rsp,0x8
38: movsx eax,al
3b: add ebx,eax
3d: lea eax,[rbx+r12*1]
41: pop rbx
42: pop rbp
43: pop r12
45: pop r13
47: ret
|
__attribute__((weak)) et extern
Cette information va être utile au lieur.
Chaque symbole (variable globale ou fonction) peut être “strong” ou weak
. Si un symbole “strong” existe, les symboles weak
seront ignorés. Si deux symboles weak
existent sans symbole “strong”, le lieur prendra aléatoirement l'un des deux (voir lieur). Si deux symboles “strong” existent, le lieur va générer une erreur duplicate symbol
.
Il est aussi possible de définir un prototype ou la déclaration d'une variable globale extern
en weak
. Dans ce cas, si le symbole n'est pas défini, le lieur ne posera pas de problème (pas de undefined reference to
) et considérera que le symbole est à l'adresse null
(ne pas lire ces symboles).
#include <iostream>
// Défini dans le header
extern int __attribute__((weak)) variable;
extern unsigned char __attribute__((weak)) variableNull;
// Défini dans le code source.
int __attribute__((weak)) variable = 1;
int main()
{
// variable a bien un pointeur non nullptr.
std::cout << reinterpret_cast<size_t>(&variable) << " vaut " << variable << "\n";
// variableNull est bien une variable avec un pointeur nullptr.
std::cout << reinterpret_cast<size_t>(&variableNull) << " est nullptr.\n";
}
Résultat dans la sortie standard:
6295640 vaut 1
0 est nullptr.