La consigne était :
« Voici deux possibilités pour implémenter new et delete de manière globale. Choisissez soit l'option A, soit l'option B ci-dessous, pas les deux! (indiquez clairement laquelle!), et implémentez ces deux opérateurs.
Consignes :
N'implémentez que ces deux opérateurs (je ne demande ni new[], ni delete[]). »
Ce qui suit est un solutionnaire possible, accompagné de brèves discussions. Le tout n'est pas exhaustif.
Une solution simple (pour vous, du moins), basée sur ce que nous avons vu en classe avec l'exemple de détecteur de fuites, serait :
void *operator new(std::size_t n) {
auto p = std::malloc(n + sizeof(std::max_align_t));
if(!p) throw std::bad_alloc{};
new (p) std::size_t{ n };
auto q = static_cast<char *>(p);
std::uninitialized_fill(q + sizeof n, q + sizeof(std::max_align_t) + n, 0x00);
return p;
}
void operator delete(void *p) {
if(!p) return;
p = static_cast<std::max_align_t*>(p) - 1;
auto n = *static_cast<std::size_t*>(p);
auto q = static_cast<char *>(p);
std::uninitialized_fill(q, q + sizeof(std::max_align_t) + n, 0xff);
free(p);
}
À noter :
Il est possible de faire plus simple si on utilise la déclinaison d'operator delete qui, depuis C++ 14, accepte un std::size_t (le nombre de bytes à lié au new correpondant), car cela nous permet de faire l'économie du jeu de cachette de n :
void *operator new(std::size_t n) {
auto p = std::malloc(n);
if(!p) throw std::bad_alloc{};
auto q = static_cast<char *>(p);
std::uninitialized_fill(q, q + n, 0x00);
return p;
}
void operator delete(void *p, std::size_t n) { // Ok depuis C++14
if(!p) return;
auto q = static_cast<char *>(p);
std::uninitialized_fill(q, q + n, 0xff);
free(p);
}
Remarque importante : écrire des valeurs dans la mémoire après l'appel à free() sera probablement un no-op en pratique avec les compilateurs contemporains. En effet, les compilateurs sont très (trop?) intelligents et, voyant l'appel à free() suivant l'écriture, peuvent considérer l'écriture comme du Dead Code et la supprimer.
Écrire un Secure Zero Memory en C++ est très difficile (nos compilateurs sont trop forts; de plus, le matériel intervient par la suite alors il n'est même pas certain que la solution soit « de niveau langage »!) et nous cherchons la bonne métaphore pour l'exprimer. Voir http://wg21.link/P1315 pour plus d'informations.
En pratique, l'option B est un peu plus simple à implémenter que l'option A, du moins si on se limite à C++ 11 et avant (l'option A se fait aisément avec C++ 14). La raison est qu'il n'est pas nécessaire de retenir la taille du bloc alloué lors du new pour implémenter le delete. Par contre, il faut placer le 0xdeadc0de de fin en tenant compte de l'alignement (je tends à être gentil quand je corrige cela).
Ça donne quelque chose comme :
void *operator new(std::size_t n) {
// n bytes de données; 0xdeadc0de au début, mais il faut allouer sizeof(std::max_align_t)
// bytes pour que les données respectent le pire alignement naturel possible
void *p = std::malloc(sizeof(std::max_align_t) + n + sizeof(unsigned int));
new (p) unsigned int{ 0xdeadc0de };
new (static_cast<char*>(p) + sizeof(std::max_align_t) + n) unsigned int{ 0xdeadc0de };
return static_cast<std::max_align_t*>(p) + 1;
}
void operator delete(void *p) {
if(!p) return;
std::free(static_cast<std::max_align_t*>(p) - 1); // note : free(nullptr) est un no-op
}
Si nous souhaitons être plus rigoureuse / rigoureux avec le positionnement du 0xdeadc0de final, nous avons :
void *operator new(std::size_t n) {
// n bytes de données; 0xdeadc0de au début, mais il faut allouer sizeof(std::max_align_t)
// bytes pour que les données respectent le pire alignement naturel possible; à la fin,
// il faut s'assurer que le 0xdeadc0de soit bien aligné aussi si on veut être rigoureuse ou
// rigoureux
const auto delta = (sizeof(std::max_align_t) - n % sizeof(std::max_align_t)) % sizeof(std::max_align_t);
void *p = std::malloc(sizeof(std::max_align_t) + n + delta + sizeof(unsigned int));
new (p) unsigned int{ 0xdeadc0de };
new (static_cast<char*>(p) + sizeof(std::max_align_t) + n + delta) unsigned int{ 0xdeadc0de };
return static_cast<std::max_align_t*>(p) + 1;
}
void operator delete(void *p) {
if(!p) return;
std::free(static_cast<std::max_align_t*>(p) - 1); // note : free(nullptr) est un no-op
}