Cum mă asigur că strtol() a fost returnat cu succes? (Programare, C, Conversie De Tip)

Jack a intrebat.

according documentation:

În caz de succes, funcția returnează numărul integral convertit ca o valoare long int. În cazul în care nu s-a putut efectua o conversie validă, se returnează o valoare zero. În cazul în care valoarea corectă este în afara intervalului de valori reprezentabile, se returnează LONG_MAX sau LONG_MIN, iar variabila globală errno este setată la ERANGE.

Luați în considerare strtol(str, (char**)NULL, 10); dacă str este "0" cum să știm dacă funcția a eșuat sau doar a convertit șirul cu "0" număr?

Comentarii

  • @StevenLuu: scanf are o gestionare și mai proastă a erorilor. Unele implementări vă vor da în general gunoi la depășire, dar fără a semnaliza eroarea în vreun fel. –  > Por Dietrich Epp.
5 răspunsuri
Daniel Fischer

Trebuie să treceți o adresă reală a pointerului dacă doriți verificarea erorilor, astfel încât să puteți distinge valorile 0 care rezultă din "0" și altele similare de valorile 0 care provin din "pqr":

char *endptr;
errno = 0;
long result = strtol(str, &endptr, 10);
if (endptr == str)
{
    // nothing parsed from the string, handle errors or exit
}
if ((result == LONG_MAX || result == LONG_MIN) && errno == ERANGE)
{
    // out of range, handle or exit
}
// all went fine, go on

Comentarii

  • Daniel: Trebuie să te întreb câteva lucruri. Poți să vii, te rog, la această discuție? –  > Por Nawaz.
  • de ce nu doar if (errno == ERANGE) pentru a verifica dacă a eșuat? –  > Por Nedefinit.
  • @Undefined Bună întrebare. Cred că [amintiri vagi] a existat un motiv pentru care verificarea ERANGE singur era problematic, iar exemplul de cod din pagina de manual face, de asemenea, verificarea valorii împreună cu ERANGE verificare. Dar am uitat complet care a fost acel motiv. Dacă a existat într-adevăr un motiv. –  > Por Daniel Fischer.
  • De fapt, acesta nu este un mod corect de a verifica dacă există erori.Nu ar trebui să verificați dacă există erori prin examinarea valorii de returnare a strtol, deoarece șirul de caractere ar putea fi o reprezentare validă a 0l, LONG_MAX sau LONG_MIN. În schimb, verificați dacă tailptr indică ceea ce vă așteptați după număr (de exemplu, „” dacă șirul ar trebui să se termine după număr). De asemenea, trebuie să ștergeți errno înainte de apel și să îl verificați după aceea, în cazul în care a existat o depășire de cursă –  > Por малин чекуров.
  • Dar nu se știe întotdeauna la ce să ne așteptăm după numărul @малинчекуров, deci cum am putea verifica dacă acesta indică ceea ce ne așteptăm? –  > Por Daniel Fischer.
малин чекуров

Din moment ce răspunsul acceptat nu este de fapt o modalitate corectă de a verifica eșecul.

Nu ar trebui să verificați dacă există erori prin examinarea valorii de returnare a strtol, deoarece șirul de caractere ar putea fi o reprezentare validă a lui 0l, LONG_MAX sau LONG_MIN. În schimb, verificați dacă tailptr indică ceea ce vă așteptați după număr (de exemplu, „” dacă șirul ar trebui să se termine după număr). De asemenea, trebuie să ștergeți errno înainte de apelare și să îl verificați după aceea, în cazul în care a existat o depășire.

paulsm4

IMHO, prefer sscanf() la atoi() sau strtol(). Motivul principal este că nu se poate verificați în mod fiabil starea de eroare pe unele platforme (de exemplu, Windows) decât dacă utilizați sscanf() (care returnează 1 dacă reușește, și 0 dacă eșuează).

Comentarii

  • De ce nu pot verifica starea de eroare pe anumite platforme? IMHO: strtol() ar putea returna o valoare negativă în caz de eșec… –  > Por Jack.
  • @Jack și long pot fi negative. –  > Por Turix.
  • scanf NU returnează un eșec la depășirea limitei. Eșecul de conversie este ușor cu strtoldoar depășirea este enervant de verificat. –  > Por Dietrich Epp.
  • Puteți să detaliați de ce, de exemplu, soluția lui Daniel Fischer nu funcționează pe Windows? Contrar documentației MSDN, se pare că timpul de execuție MSC errno de execuție MSC este thread-safe, a se vedea stackoverflow.com/questions/6413052/msvc-errno-thread-safety –  > Por Dietrich Epp.
Turix

Puteți fie să verificați errno fie să treceți o valoare non-NULL pentru al doilea argument și să comparați valoarea rezultată cu str, de exemplu:

char * endptr;
long result = strtol(str, &endptr, 10);
if (endptr > str)
{
    // Use result...
}

cmwt

Cred că am verificat toate cazurile limită. Dacă cineva se gândește la un caz limită pe care l-am omis, vă rog să mă anunțați în comentarii și voi actualiza această postare. Am încercat să păstrez mesajele de eroare simple. Dacă nu sunteți de acord cu această decizie, vă rog să nu ezitați să le modificați după cum credeți de cuviință.

// Copyright (C) 2021 by cmwt
// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

#include <errno.h>    // errno, ERANGE
#include <limits.h>   // LONG_MAX, LONG_MIN
#include <stdbool.h>  // true, false
#include <stddef.h>   // ptrdiff_t
#include <stdio.h>    // printf()
#include <stdlib.h>   // strtol()

struct ResultToLong {
  const char *err_msg;
  long        answer;
  ptrdiff_t   num_read;
  bool        is_func_success;
};

struct ResultToLong stringToLong(const char* start, int base) {
  struct ResultToLong result = {NULL, 0, 0, false};
  int save_errno = 0;
  char *end = NULL;

  if (base < 0 || base > 36) {
    result.err_msg = "Bad base: expect (0 <= base <= 36)";
    return result;
  }
  if (start == NULL) {
    result.err_msg = "Bad start: expect (start != NULL)";
    return result;
  }
  if (*start == '') {
    result.err_msg = "Bad start: start empty (const char* start == "";)";
    return result;
  }

  errno = 0;
  result.answer = strtol(start, &end, base);
  save_errno = errno;

  if (result.answer == 0 && *(start - 1) != '0') {
    result.err_msg = "Bad start: not a number";
    result.num_read = end - start;
    return result;
  }
  if (result.answer == LONG_MIN && save_errno == ERANGE) {
    result.err_msg = "Bad start: result < LONG_MIN";
    result.num_read = end - start;
    return result;
  }
  if (result.answer == LONG_MAX && save_errno == ERANGE) {
    result.err_msg = "Bad start: result > LONG_MAX";
    result.num_read = end - start;
    return result;
  }
  if (*end != '') {
  result.err_msg = "Warning: number in start is not '\0' terminated";
  result.num_read = end - start;
  result.is_func_success = true;
  return result;
  }

  result.err_msg = "Success";
  result.num_read = end - start;
  result.is_func_success = true;
  return result;
}

int main() {
  struct ResultToLong result;
  const char* str;
  
  printf("Starting...

");

  str= NULL;
  result = stringToLong(str, 0);
  printf("Response message: %s
", result.err_msg);
  printf("Input string: %s
", "<NULL>");
  printf("Number of chars processed: %ld
", result.num_read);
  printf("Result: %ld

", result.answer);
  
  str= "";
  result = stringToLong(str, 0);
  printf("Response message: %s
", result.err_msg);
  printf("Input string: '%s'
", str);
  printf("Number of chars processed: %ld
", result.num_read);
  printf("Result: %ld

", result.answer);

  str= "42";
  result = stringToLong(str, -1);
  printf("Response message: %s
", result.err_msg);
  printf("Input string: '%s'
", str);
  printf("Number of chars processed: %ld
", result.num_read);
  printf("Result: %ld

", result.answer);

  str= "42";
  result = stringToLong(str, 0);
  printf("Response message: %s
", result.err_msg);
  printf("Input string: '%s'
", str);
  printf("Number of chars processed: %ld
", result.num_read);
  printf("Result: %ld

", result.answer);

  str= "42 ";
  result = stringToLong(str, 0);
  printf("Response message: %s
", result.err_msg);
  printf("Input string: '%s'
", str);
  printf("Number of chars processed: %ld
", result.num_read);
  printf("Result: %ld

", result.answer);

  str= "                42";
  result = stringToLong(str, 0);
  printf("Response message: %s
", result.err_msg);
  printf("Input string: '%s'
", str);
  printf("Number of chars processed: %ld
", result.num_read);
  printf("Result: %ld

", result.answer);

  str= "0x42";
  result = stringToLong(str, 0);
  printf("Response message: %s
", result.err_msg);
  printf("Input string: '%s'
", str);
  printf("Number of chars processed: %ld
", result.num_read);
  printf("Result: %ld

", result.answer);

  str= "042";
  result = stringToLong(str, 0);
  printf("Response message: %s
", result.err_msg);
  printf("Input string: '%s'
", str);
  printf("Number of chars processed: %ld
", result.num_read);
  printf("Result: %ld

", result.answer);

  str= "+9999999999999999999";
  result = stringToLong(str, 0);
  printf("Response message: %s
", result.err_msg);
  printf("Input string: '%s'
", str);
  printf("Number of chars processed: %ld
", result.num_read);
  printf("Result: %ld

", result.answer);

  str= "-9999999999999999999";
  result = stringToLong(str, 0);
  printf("Response message: %s
", result.err_msg);
  printf("Input string: '%s'
", str);
  printf("Number of chars processed: %ld
", result.num_read);
  printf("Result: %ld

", result.answer);

  str= "?";
  result = stringToLong(str, 0);
  printf("Response message: %s
", result.err_msg);
  printf("Input string: '%s'
", str);
  printf("Number of chars processed: %ld
", result.num_read);
  printf("Result: %ld

", result.answer);

  printf("Done.
");
}

Dacă aveți obiecții cu privire la returnarea unei structuri de această dimensiune prin valoare, atunci treceți un pointer la structură ca argument suplimentar, dar nu uitați să tratați cazul în care pointerul structurii este NULL. Scopul meu a fost să fac acest cod ușor de înțeles. Probabil că doriți să combinați verificările și să centralizați stabilirea valorilor de returnare.

Alte gânduri

Mi-aș dori strtol() să returneze și locul de unde începe numărul. Ați putea afla acest lucru prin iterația înapoi de la pointerul final, dar ar putea fi lent.