Waarom krijg ik een cast from pointer to integer of verschillende size fout?

De volgende regel (pure c) compileert netjes op windows(win7 64 bits + codeblocks 13 + mingw32) en debian(wheezy 32 bits + codeblocks 10 + gcc) maar geeft waarschuwing op kali(64 bits + codeblokken + gcc). Eventuele opmerkingen? Ik bedoel, waarom krijg ik deze waarschuwing, hoewel dezelfde regel compileert zonder enige waarschuwing op Windows & debian?

void* foo(void *dst, ...) {
    // some code
    unsigned int blkLen = sizeof(int); // this line ok.
    unsigned int offset = (unsigned int) dst % blkLen; // warning here!
    // some code cont...
}

Het bericht in codeblokken is: “error:cast from pointer naar integer van verschillende grootte [-Werror=pointer-to-int-cast]”

opmerking: mijn compileropties zijn -std=c99 -Werror -save-temps(hetzelfde op alle drie de systemen).

bewerk 2:
Hoewel het me is gelukt om het zonder waarschuwing te laten compileren met behulp van de onderstaande preprocessor-regels,
@Keith Thompson (zie hieronder) heeft een cruciaal punt over de kwestie. Dus mijn laatste beslissing is dat het gebruik van uintptr_teen betere keuze zou zijn.

bewerk 1:
Bedankt voor iedereen gereageerd. Zoals alle antwoorden opmerken, is het probleem een ​​32-bits versus 64-bits probleem. Ik heb de volgende preprocessorregels ingevoegd:

#if __linux__   //  or #if __GNUC__
    #if __x86_64__ || __ppc64__
        #define ENVIRONMENT64
    #else
        #define ENVIRONMENT32
    #endif
#else
    #if _WIN32
        #define ENVIRONMENT32
    #else
        #define ENVIRONMENT64
    #endif
#endif // __linux__
#ifdef ENVIRONMENT64
    #define MAX_BLOCK_SIZE unsigned long long int
#else
    #define MAX_BLOCK_SIZE unsigned long int
#endif // ENVIRONMENT64

en verving vervolgens de probleemregel als:

unsigned int offset = (MAX_BLOCK_SIZE) dst % blkLen;

Nu lijkt alles in orde.


Antwoord 1, autoriteit 100%

De reden voor de waarschuwing is dat de compiler vermoedt dat u een pointer probeert te omleiden via inten terug. Dit was gebruikelijk vóór de komst van 64-bits machines en het is niet veilig of redelijk. Natuurlijk kan de compiler hier duidelijk zien dat je dit niet doet, en het zou leuk zijn als het slim genoeg was om de waarschuwing in dit soort gevallen te vermijden, maar dat is het niet.

Een schoon alternatief dat de waarschuwing vermijdt, en een ander veel vervelender probleem van een verkeerd resultaat wanneer de geconverteerde waarde negatief is, is:

unsigned int offset = (uintptr_t) dst % blkLen;

Je moet stdint.hof inttypes.hopnemen om uintptr_tbeschikbaar te hebben.


Antwoord 2, autoriteit 54%

Het probleem is dat het converteren van een void*-aanwijzer naar unsigned intinherent niet-draagbaar is.

Het mogelijke verschil in grootte is slechts een deel van het probleem. Dat deel van het probleem kan worden opgelost door uintptr_tte gebruiken, een type gedefinieerd in <stdint.h>en <inttypes.h>. uintptr_tis gegarandeerd breed genoeg dat het converteren van een void*naar uintptr_ten weer terug de originele pointerwaarde oplevert (of op zijn minst een pointer waarde die vergelijkbaar is met de originele). Er is ook een type intptr_t, dat is ondertekend; meestal zijn niet-ondertekende typen logischer voor dit soort dingen. uintptr_ten intptr_tbestaan ​​niet gegarandeerd, maar ze zouden moeten bestaan ​​op elke (C99 of later) implementatie die de juiste integer-types heeft.

Maar zelfs als je een geheel getal hebt dat groot genoeg is voor een geconverteerde aanwijzer, is het resultaat niet per se zinvol voor iets anders dan het terug converteren naar een aanwijzer.

De C-standaard zegt in een niet-normatieve voetnoot dat:

De toewijzingsfuncties voor het converteren van een aanwijzer naar een geheel getal of een
integer naar een pointer zijn bedoeld om consistent te zijn met de adressering
structuur van de uitvoeringsomgeving.

wat niet helpt, tenzij je toevallig weet wat die adresseringsstructuur is.

Het lijkt erop dat u probeert te bepalen wat de offset van het argument void*is ten opzichte van het eerstvolgende lagere veelvoud van blkLen; met andere woorden, u probeert te bepalen hoe de aanwijzerwaarde is uitgelijndten opzichte van blkLen-geheugenblokken.

Als je toevallig weet dat dit verstandig is om te doen op het systeem dat je gebruikt, dan is dat prima. Maar u moet zich ervan bewust zijn dat rekenkundige bewerkingen op gehele getallen die het resultaat zijn van aanwijzerconversies, nog steeds inherent niet-draagbaar zijn.

Een concreet voorbeeld: ik heb gewerkt aan systemen (Cray-vectormachines) waar een void*-aanwijzer een 64-bits machine-adres is (dat verwijst naar een 64-bits woord), met een 3-bit byte offset ingevoegd door software in de anders ongebruikte hoge-orde3 bits. Het converteren van een aanwijzer naar een geheel getal kopieerde eenvoudig de weergave. Elke berekening van gehele getallen op zo’n geheel getal levert waarschijnlijk nietszeggende resultaten op, tenzij er rekening wordt gehouden met deze (weliswaar exotische) weergave.

Conclusies:

  1. Je moet beslist uintptr_tgebruiken in plaats van preprocessortrucs uit te halen om te bepalen welk integer type je kunt gebruiken. De uitvoerder van uw compiler heeft al het werk gedaan om een ​​integer type te bepalen dat veilig een geconverteerde pointerwaarde kan bevatten. Het is niet nodig om dat specifieke wiel opnieuw uit te vinden. (Waarschuwing: <stdint.h>is toegevoegd aan C door de ISO-standaard van 1999. Als je vastzit aan het gebruik van een oude compiler die het niet implementeert, moet je misschien nog steeds een soort van #ifdefhacks. Maar ik raad toch aan om uintptr_tte gebruiken als het beschikbaar is. Je kunt __STDC_VERSION__ >= 199901Ltesten om te testen op C99 conformiteit — hoewel sommige compilers <stdint.h>ondersteunen zonder C99 volledig te ondersteunen.)

  2. Je moet er rekening mee houden dat het niet overdraagbaar is om een ​​aanwijzer naar een geheel getal te converteren en met zijn waarde te spelen. Dat wil niet zeggen dat je het niet zou moeten doen; een van de sterkste punten van C is de mogelijkheid om niet-draagbarecode te ondersteunen wanneer dat is wat je nodig hebt.


Antwoord 3, autoriteit 15%

Omdat het casten van een void *naar een unsigned intprecies is wat die waarschuwing moet vangen omdat het onveilig is. De aanwijzer kan 64-bits zijn en de intkan 32-bits zijn. Voor een bepaald platform is de sizeof(unsigned int)niet gegarandeerd sizeof(void *). Gebruik in plaats daarvan uintptr_t.


Antwoord 4, autoriteit 8%

Misschien omdat op een 64-bits architectuur een pointer 64 bits lang is en een int slechts 32 bits lang?

Probeer het

void* foo(void *dst, ...) {
    // some code
    unsigned int blkLen = sizeof(int); // this line ok.
    uintptr_t offset = (uintptr_t) dst % blkLen; // warning here!
    // some code cont...
}

Antwoord 5, autoriteit 4%

Ik denk dat je de waarschuwing krijgt omdat de grootte van int afhangt van de implementaties, bijvoorbeeld int kan 2 byte lang of 4 byte lang zijn. Dit kan de reden zijn voor de waarschuwing (corrigeer me als ik het mis heb). Maar hoe dan ook, waarom probeer je een modulo op een pointer te doen.


Antwoord 6

Je hebt de macro gemaakt, maar denk niet dat het nog steeds verkeerd is. Omdat uw aanwijzer wordt geconverteerd naar unsigned long long int of unsigned long int die 32 bit en 64 bit zal zijn in 86x en 64x OS, maar uw variabele offset is unsigned int die 32bit is in 64x en 86x OS.
Dus ik denk dat je offset ook naar de respectievelijke macro moet converteren.

Of je kunt de aanwijzer ook eenvoudig omzetten naar long (d.w.z. unsigned int naar long) en offset naar long (d.w.z. unsigned int naar long).

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Other episodes