Waarom zijn niet-ondertekende gehele getallen foutgevoelig?

Ik keek naar deze video. Bjarne Stroustrupzegt dat unsigned intsfoutgevoelig zijn en tot bugs leiden. Je moet ze dus alleen gebruiken als je ze echt nodig hebt. Ik heb ook in een van de vragen over Stack Overflow (maar ik weet niet meer welke) gelezen dat het gebruik van unsigned intstot beveiligingsbugs kan leiden.

Hoe leiden ze tot beveiligingsbugs? Kan iemand het duidelijk uitleggen door een passend voorbeeld te geven?


Antwoord 1, autoriteit 100%

Een mogelijk aspect is dat niet-ondertekende gehele getallen kunnen leiden tot enigszins moeilijk te herkennen problemen in lussen, omdat de onderstroom tot grote getallen leidt. Ik kan niet tellen (zelfs met een geheel getal zonder teken!) hoe vaak ik een variant van deze bug heb gemaakt

for(size_t i = foo.size(); i >= 0; --i)
    ...

Merk op dat, per definitie, i >= 0altijd waar is. (Wat dit in de eerste plaats veroorzaakt, is dat als iis ondertekend, de compiler zal waarschuwen voor een mogelijke overloop met de size_tvan size()).

Er zijn andere redenen genoemd Gevaar – hier worden niet-ondertekende typen gebruikt!, waarvan naar mijn mening de impliciete typeconversie tussen ondertekend en niet-ondertekend is.


Antwoord 2, autoriteit 73%

Een grote factor is dat het luslogica moeilijker maakt: stel je voor dat je alles wilt herhalen, behalve het laatste element van een array (wat in de echte wereld wel gebeurt). Dus je schrijft je functie:

void fun (const std::vector<int> &vec) {
    for (std::size_t i = 0; i < vec.size() - 1; ++i)
        do_something(vec[i]);
}

Ziet er goed uit, nietwaar? Het compileert zelfs netjes met zeer hoge waarschuwingsniveaus! (Live) Dus je zet dit in je code, alle tests verlopen soepel en je vergeet erover.

Nu, later komt er iemand langs en geeft een lege vectordoor aan uw functie. Nu met een ondertekend geheel getal, had je hopelijk de sign-compare compiler-waarschuwingopgemerkt, de juiste cast heeft geïntroduceerd en de buggy-code in de eerste plaats niet heeft gepubliceerd.

Maar in uw implementatie met het niet-ondertekende gehele getal, wikkelt u en wordt de lusvoorwaarde i < SIZE_T_MAX. Ramp, UB en waarschijnlijk crash!

Ik wil weten hoe ze tot beveiligingsbugs leiden?

Dit is ook een beveiligingsprobleem, met name een bufferoverloop. Een manier om hier mogelijk misbruik van te maken zou zijn als do_somethingiets zou doen dat door de aanvaller kan worden waargenomen. Ze kunnen misschien achterhalen welke invoer in do_somethingis gegaan, en op die manier zouden gegevens uit je geheugen worden gelekt waartoe de aanvaller geen toegang zou moeten hebben. Dit zou een scenario zijn dat lijkt op de Heartbleed-bug. (Met dank aan ratchet freak voor het erop wijzen in een opmerking .)


Antwoord 3, autoriteit 49%

Ik ga een video niet alleen bekijken om een ​​vraag te beantwoorden, maar een probleem zijn de verwarrende conversies die kunnen optreden als u ondertekende en niet-ondertekende waarden combineert. Bijvoorbeeld:

#include <iostream>
int main() {
    unsigned n = 42;
    int i = -42;
    if (i < n) {
        std::cout << "All is well\n";
    } else {
        std::cout << "ARITHMETIC IS BROKEN!\n";
    }
}

De promotieregels houden in dat iwordt geconverteerd naar unsignedvoor de vergelijking, wat een groot positief getal en een verrassend resultaat oplevert.


Antwoord 4, autoriteit 22%

Hoewel het alleen kan worden beschouwd als een variant van de bestaande antwoorden: Verwijzend naar “Ondertekend en niet-ondertekend typen in interfaces”, C++ Report, september 1995door Scott Meyers, is het vooral belangrijk om niet-ondertekende typen in interfaceste vermijden.

Het probleem is dat het onmogelijk wordt om bepaalde fouten te detecteren die clients van de interface zouden kunnen maken (en als ze ze konmaken, zullen ze zemaken).

Het daar gegeven voorbeeld is:

template <class T>
  class Array {
  public:
      Array(unsigned int size);
  ...

en een mogelijke instantie van deze klasse

int f(); // f and g are functions that return
int g(); // ints; what they do is unimportant
Array<double> a(f()-g()); // array size is f()-g()

Het verschil tussen de waarden die worden geretourneerd door f()en g()kan om een ​​aantal redenen negatief zijn. De constructor van de klasse Arrayontvangt dit verschil als een waarde die impliciet wordt omgezet in unsigned. Als implementator van de klasse Arraykan men dus geen onderscheid maken tussen een foutief doorgegeven waarde van -1en een zeer grote arraytoewijzing.


Antwoord 5, autoriteit 8%

Het grote probleem met niet-ondertekende int is dat als je 1 aftrekt van een niet-ondertekende int 0, het resultaat geen negatief getal is, het resultaat niet minder is dan het getal waarmee je begon, maar het resultaat is het grootst mogelijke niet-ondertekende int-waarde.

unsigned int x = 0;
unsigned int y = x - 1;
if (y > x) printf ("What a surprise! \n");

En dit is wat niet-ondertekende int foutgevoelig maakt. Natuurlijk werkt unsigned int precies zoals het is ontworpen om te werken. Het is absoluut veilig als u weet wat u doet en geen fouten maakt. Maar de meeste mensen maken fouten.

Als je een goede compiler gebruikt, zet je alle waarschuwingen aan die de compiler produceert, en hij zal je vertellen wanneer je gevaarlijke dingen doet die waarschijnlijk fouten zijn.

Other episodes