Diskuze: přetečení
V předchozím kvízu, Online test znalostí C++, jsme si ověřili nabyté zkušenosti z kurzu.
Člen
Zobrazeno 29 zpráv z 29.
//= Settings::TRACKING_CODE_B ?> //= Settings::TRACKING_CODE ?>
V předchozím kvízu, Online test znalostí C++, jsme si ověřili nabyté zkušenosti z kurzu.
úplně jednoduše - dej proměnné typ long, případně long long
ale vážně - porovnej hodnotu s max hodnotou pro int - https://www.tutorialspoint.com/…limits_h.htm
Predpokladam, ze nemyslis provnat to matematicky.
V céčku by bola optimálna funkcia, ktorá berie ako parameter reťazec a vracia int, ak spĺňa parametre int.
Otestuj.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
#define TESTY // pri zakomentovani budu testy odstavene
int parse_int(char *number)
{
char *pom;
pom = (char*)malloc(128);
sprintf(pom, "%d", atoi(number));
if (strcmp(number, pom)) {
printf("Cislo nie je int!\n");
return -1;
}
return atoi(number);
}
#ifdef TESTY
#define TESTY
int main(void)
{
/** testy funkcie int parse_int(char *number) **/
assert(parse_int("123456") == 123456);
assert(parse_int("-123456") == -123456);
assert(parse_int("2147483647") == INT_MAX);
assert(parse_int("-2147483648") == INT_MIN);
assert(parse_int("2147483657") == -1);
assert(parse_int("2147483747") == -1);
assert(parse_int("2147484647") == -1);
assert(parse_int("2147493647") == -1);
assert(parse_int("2147583647") == -1);
assert(parse_int("2148483647") == -1);
assert(parse_int("2157483647") == -1);
assert(parse_int("2247483647") == -1);
assert(parse_int("3147483647") == -1);
assert(parse_int("2147483549") == 2147483549);
assert(parse_int("2146499999") == 2146499999);
assert(parse_int("2099999999") == 2099999999);
assert(parse_int("2157483647265847589565888885445588555326598") == -1);
printf("\n\nTesty presli.\n");
return 0;
}
#endif // TESTY
Tohle nebude fungovat, pokud chceš podporovat i čísla v jiné než desítkové soustavě, protože třeba 0x10 není řetězcově rovno 16. Plus tam alokuješ paměť, kterou neuvolňuješ. Vzhledem k tomu, kolik %d zabere max. znaků, ani alokace není třeba, stačí dostatečně velká lokální proměnná na zásobníku.
Nebylo by nejlepší použít třeba strtoll a náslecně porovnat s maximální/minimální hodnotou intu?
Aby nikdo nemohl zadat do proměnné číslo tak aby nepřeteklo nezaručíš nikdy. Nepozorný programátor Ti ji naplní nesmyslnou hodnotou velmi snadno, třeba při inicializaci nebo přiřazení. Při překladu se mu zobrazí varovné hlášení, ale ne chybové. Viz. fragment kódu:
#include <limits.h>
#include <stdio.h>
...
int i = 1000000000000;
if((i>=INT_MIN)&&(i<=INT_MAX)) printf("%d", i);
else printf("i je mimo rozsah.\n");
...
Ikdyž je hodnota i evidentně mimo rozsah, hláška o tom, že tomu tak je, se nezobrazí.
Takže jakým způsobem, odkud se očekává hodnota proměnné typu int ze strany uživatele (ze souboru, z konzole, jako argument příkazového řádku, prostřednictvím síťové komunikace, atd)?
Nepredpokladal som, ze by chcel pouzit cisla ine ako desiatkovu sustavu a pre nu to funguje bezvadne. Ano mas pravdu v alokacii. Malo to byt staticky.
Ako som už písal vyššie, vstup musí byť string.
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
int parse_int(char *number)
{
char pom[128];
sprintf(pom, "%d", atoi(number));
if (strcmp(number, pom)) {
cout << "Cislo nie je int!" << endl;
return -1;
}
return atoi(number);
}
int main(void)
{
char cislo[] = "10000000000000000";
parse_int(cislo);
return 0;
}
Ale funguje to iba na typ int.
Myslím, že toto by mohla byť odpoveď na prvú otázku.
Přesněji řečeno vstupní data by měla být implementována a uložena do proměnné typu pole znaků. Předtím by však měla být otestována na paměťový rozsah a vyhodnocena na platnost, aby se nám do pole vešla. Zde už pracujeme s hotovou hodnotou. Řešení je to ale pěkné, neboť funkce atoi() dělá práci za nás. V případě přetečení rozsahu vrací hodnotu INT_MIN respektive INT_MAX což ve spojitosti s funkcí strcmp() dává požadovaný výsledek. Jedna věc tam ale není správná a to návratová hodnota funkce parse_int(). Nelze vracet hodnotu -1 v případě, že číslo není v daném rozsahu, neboť hodnota -1 koliduje s návratovou hodnotou získanou pomocí funkce atoi() v případě, že testovaným číslem je -1 a není tak zřejmé, jaká situace nastala.
Nevím, zda by nebylo elegantnější zkusit strtol, která by měla v případě, že se zadaná hodnota do typu nevejde, nastavovat errno na ERANGE. Je tam ale třeba dávat pozor i na to, zda vůbec na vstupu číslo je, popř. zda nám stačí, pokud vstupní řetězec (resp. proměnná) číslem začíná, nebo zda musí obsahovat pouze číslo v řetězcové podobě. Chce to zkrátka pořádně přečíst dokumentaci a popřemýšlet.
Hm a to atoi-strcmp řešení stejně nebude fungovat pro řetězce ve stylu 00025.
Tie nuly ma nenapadly, ale staci mala uprava funkcie a bude to ok.
Ano, stačí malá úprava. Ale už mi to začíná připadat jako takové flikování... .
Aj problem s -1 sa da jednoducho vyriesit.
Sorry, retazec -1 spracuje spravne a vrati cislo -1.
Blbnem.
Fragment kódu za použití funkce strtol()
unsigned int i = 0;
long int li;
char cislo[][15] = { "0", "-0", "+0", "100", "-100", "+100", "01000", "-01000", "+01000",
"2147483647", "-2147483648", "2147483648", "-2147483649", "" };
...
printf("%-16s%-16s%s\n\n", "Retezec", "Status", "Cislo");
while (strcmp(cislo[i], "")) {
errno = 0;
li = strtol(cislo[i], NULL, 10);
if (errno==ERANGE) printf("%-16s%-16s%ld\n", cislo[i], "Mimo rozsah", li);
else printf("%-16s%-16s%ld\n", cislo[i], "OK", li);
i++;
}
...
I funkce atoi() resp. atol() nastavují při přetečení hodnotu makra errno na ERANGE. Výhodou funkce strtol() je ale možnost velmi snadno pracovat s konkrétní číselnou soustavou, možnost zastavit skenování a širší zpracování bílých znaků, jako třeba již zmíněné nuly. Použití funkce strtol() je tedy lepším řešením..
Ešte vyriešiť napr. takýto string "2548R569", aby to vpísalo mimo rozsah
a nie "2548R569" OK 2548.
Preklep sa užívateľovi priptrafí.
while (strcmp(cislo[i], "")) {
errno = 0;
if ((cislo[i][0] != '+') && (cislo[i][0] != '-') && (!isdigit(cislo[i][0]))) errno = ERANGE;
else {
for (j = 1; cislo[i][j]; j++) {
if (!isdigit(cislo[i][j])) {
errno = ERANGE;
break;
}
}
if (!errno) li = strtol(cislo[i], NULL, 10);
}
if (!errno) printf("%-16s%-16s%ld\n", cislo[i], "OK", li);
else printf("%-16s%-16s\n", cislo[i], "Mimo rozsah");
i++;
}
Doplněno o platné znaky na konkrétních pozicích znakového pole..
To bych spíš využil druhého parametru funkce strtol, abych nemusel provádět psí kusy a dělat v podstatě již to, co ta funkce tak jako tak provádí.
while (strcmp(cislo[i], "")) {
char *endptr = cislo[i];
errno = 0;
li = strtol(cislo[i], &endptr, 10);
if (*endptr != '\0')
errno = ERANGE;
if (errno==ERANGE) printf("%-16s%-16s%ld\n", cislo[i], "Mimo rozsah", li);
else printf("%-16s%-16s%ld\n", cislo[i], "OK", li);
i++;
}
li = strtol(cislo[i], &endptr, 10);
if (*endptr != '\0')
errno = ERANGE;
Správne som pochopil, že 10 znamená prevod na typ int a *endptr != '\0' znamená, že typ nie je int?
10 znamená, že strtol bude akceptovat pouze řetězce zapisující čísla v desítkové soustavě. Podle mě je tato specifikace zbytečná. Obvykle dávám nulu (libovolná soustava, což efektivně znamená binární, osmičkovou, desítkovou či šestnáctkovou; záleží na prefixu čísla).
Argument endptr ti funkce naplní adresou znaku v řetězci, který se nachází hned za posledním znakem podřetězce zkonvertovaného na číslo. Takže když máš třeba řetězec 123R456, tak po jednom zavolání strtol bude endptr odkazovat na řetězec R456.
Pokud nechceš tyhle "částečné" konverze, tzn. pokud potřebuješ, aby se buď zkonvertoval celý řetězec, nebo nic, tak potřebuješ, aby po úspěšném zavolání strtol argument endptr odkazoval na konec řetězec, tedy na nulový znak.
Je ale pravda, že ta moje implementace může potlačit jiné chyby reportované fcí strtol (nebo je převede na ERANGE), což nemusí být to pravé ořechové.
Ona i ta moje implementace přiřazovat makru errno hodnotu ERANGE u testu na platný znak asi nebude úplně správná. A tak zde přikládám kosmeticky upravenou implementaci testování.
while (strcmp(cislo[i], "")) {
errno = 0;
endptr = cislo[i];
li = strtol(cislo[i], &endptr, 10);
if ((*endptr != '\0') || (errno)) printf("%-16s%-16s\n", cislo[i], "Mimo rozsah");
else printf("%-16s%-16s%ld\n", cislo[i], "OK", li);
i++;
}
Zobrazeno 29 zpráv z 29.