Bir işaretçiyi tam sayıya dönüştürme


88

Mevcut bir kodu 64 bitlik bir makineye uyarlamaya çalışıyorum. Temel sorun, bir işlevde, önceki kodlayıcının işlevin kendisinde uygun türe dönüştürülen bir void * bağımsız değişkeni kullanmasıdır. Kısa bir örnek:

void function(MESSAGE_ID id, void* param)
{
    if(id == FOO) {
        int real_param = (int)param;
        // ...
    }
}

Tabii ki 64 bitlik bir makinede şu hatayı alıyorum:

error: cast from 'void*' to 'int' loses precision

Hala 32 bitlik bir makinede ve olabildiğince temiz çalışacak şekilde bunu düzeltmek istiyorum. Herhangi bir fikir ?


5
Bunun eski bir yazıyı kazdığını biliyorum, ancak kabul edilen cevap pek doğru değil gibi görünüyor. Çalışmamanın somut bir örneği size_ti386 parçalı bellektir. 32 bit makinede rağmen, sizeofdöner 2için size_t. Alex'in aşağıdaki cevabı doğru görünüyor. Alex'in cevabı ve uintptr_themen hemen her yerde çalışıyor ve artık standart. C ++ 11 tedavisi sağlar ve hatta C ++ 03 başlık korumalarını verir.
jww

Yanıtlar:


70

Kullanım intptr_tve uintptr_t.

Taşınabilir bir şekilde tanımlandığından emin olmak için, şu şekilde kod kullanabilirsiniz:

#if defined(__BORLANDC__)
    typedef unsigned char uint8_t;
    typedef __int64 int64_t;
    typedef unsigned long uintptr_t;
#elif defined(_MSC_VER)
    typedef unsigned char uint8_t;
    typedef __int64 int64_t;
#else
    #include <stdint.h>
#endif

Bunu bir .h dosyasına koyun ve ihtiyacınız olan yere ekleyin.

Alternatif olarak, Microsoft'un sürümünü indirebilirsiniz stdint.hdosyanın burada ya bir taşınabilir birini kullanın burada .


MSVC (ve muhtemelen Borland) ile çalışan bir stdint.h'nin nasıl alınacağı hakkında bilgi için stackoverflow.com/questions/126279/… sayfasına bakın .
Michael Burr

2
Her iki bağlantı da koptu!
Antonio

1
Bu cevap C ile ilgilidir, ancak dil C ++ olarak etiketlenmiştir, bu yüzden aradığım cevap bu değildir.
HaseeB Mir

@HaSeeBMiR Uygun bir düzeltme geçmektir <cstdint>veya uygun düzgün indirmek için cstdintbir indirirseniz stdint.h.
Justin Time - Monica'yı yeniden

1
@HaSeeBMiR Cevabın C ++ yerine C ile ilgili olmasının tek nedeni, eşdeğer C ++ başlığı yerine bir C başlığı kullanmasıdır. C ön işlemcisi, C ++ ' cstdintnın bir parçasıdır ve orada tanımlanan tür adlarının tümü gibi C ++ standardının bir parçasıdır. Belirtilen etiketler için gerçekten uygundur. ... Türleri manuel olarak tanımlamaya katılmıyorum, ancak bunu yapmayan derleyicilerle çalışırken gerekli olabilir.
Justin Time -

94

Bunun modern C ++ yolu olduğunu söyleyebilirim.

#include <cstdint>
void *p;
auto i = reinterpret_cast<std::uintptr_t>(p);

DÜZENLE :

Tamsayı için doğru tür

dolayısıyla bir işaretçiyi tam sayı olarak saklamanın doğru yolu uintptr_tveya intptr_ttürlerini kullanmaktır . (Ayrıca C99 için cppreference tam sayı türlerine bakın ).

bu türler <stdint.h>C99 için ve stdC ++ 11 için ad alanında tanımlanır <cstdint>( C ++ için tam sayı türlerine bakın ).

C ++ 11 (ve sonrası) Sürüm

#include <cstdint>
std::uintptr_t i;

C ++ 03 Sürümü

extern "C" {
#include <stdint.h>
}

uintptr_t i;

C99 Sürümü

#include <stdint.h>
uintptr_t i;

Doğru döküm operatörü

C'de yalnızca bir cast vardır ve C ++ 'da C dökümünü kullanmak hoş karşılanmaz (bu yüzden C ++' da kullanmayın). C ++ 'da farklı yayınlar vardır. reinterpret_castbu dönüşüm için doğru atama (ayrıca buraya bakın ).

C ++ 11 Sürümü

auto i = reinterpret_cast<std::uintptr_t>(p);

C ++ 03 Sürümü

uintptr_t i = reinterpret_cast<uintptr_t>(p);

C Sürümü

uintptr_t i = (uintptr_t)p; // C Version

ilgili sorular


6
doğru bir şekilde reinterpret_cast'den bahseden tek cevap
plasmacel

<cstdint> eklemeyi düşündüyseniz, muhtemelen bunun yerine std :: uintptr_t'yi de kullanmak istersiniz.
linleno

Harika ... Oyuncu kadrosu aradığım şeydi. uintptr_tBunun yerine kullanmamız söylendiğinde size_t, neden gerekli reinterpret_cast? static_castStandart özellikle uyumlu veri türlerini sağladığından, yapılması gereken basit bir şey gibi görünüyor ...
jww

1
@jww read: en.cppreference.com/w/cpp/language/static_cast Buradaki anlayışım static_cast, türü dönüştürebileceğidir veya bir işaretçi, türün ihtiyacı varsa işaretçi ayarlamaları yapabilir. reinterpret_castgerçekten sadece temeldeki bellek modelinin türünü değiştirmektir (mutasyon yok). açıklığa kavuşturmak için: static_castburada aynı şekilde davranıyor.
Alexander Oh

2
C ve C ++ 'da nasıl yayın yapılacağıyla ilgili tüm ayrıntıları sağladığı için bunun yerine seçili yanıt olarak işaretlenmelidir .
HaseeB Mir

43

Mimarinize (her ne olursa olsun) uyması için 'size_t' ve 'ptrdiff_t' gereklidir. Bu nedenle, 'int' kullanmak yerine, 64 bitlik bir sistemde 64 bit tip olması gereken 'size_t' kullanabilmeniz gerektiğini düşünüyorum.

Bu tartışma unsigned int vs size_t biraz daha ayrıntıya giriyor.


34
Size_t genellikle bir işaretçiyi tutacak kadar büyük olsa da, durum böyle değildir. Bir stdint.h üstbilgisi bulmak (derleyicinizde zaten yoksa) ve uintptr_t kullanmak daha iyi olacaktır.
Michael Burr

3
Ne yazık ki tek kısıtlama size_t, herhangi birinin sonucunu tutması gerektiğidir sizeof(). Bu, x64'te mutlaka 64 bit yapmaz. ayrıca bkz
Antoine

3
size_t olabilir güvenli olmayan bir elemanı işaretçi değerini depolar. Bkz. En.cppreference.com/w/cpp/types/size_t .
AndyJost

2
@AndyJost Hayır yapamaz. Kendi bağlantınız bile bunu doğrular.
yyny

1
@YoYoYonnY: "Pek çok platformda (bölütlü adresleme sistemlerinde bir istisna vardır) std :: size_t herhangi bir üye olmayan göstericinin değerini güvenli bir şekilde saklayabilir, bu durumda std :: uintptr_t ile eşanlamlıdır." - neden bahsediyorsun?
slashmais


8

Çeşitli cevaplar üzerinde yoğunlaşmaktadır uintptr_tve #include <stdint.h>'çözüm olarak. Bence cevabın bir parçası, ama cevabın tamamı değil. Ayrıca, FOO'nun ileti kimliği ile işlevin nerede çağrıldığına da bakmanız gerekir.

Bu kodu ve derlemeyi düşünün:

$ cat kk.c
#include <stdio.h>
static void function(int n, void *p)
{
    unsigned long z = *(unsigned long *)p;
    printf("%d - %lu\n", n, z);
}

int main(void)
{
    function(1, 2);
    return(0);
}
$ rmk kk
        gcc -m64 -g -O -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith \
            -Wcast-qual -Wstrict-prototypes -Wmissing-prototypes \
            -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE kk.c -o kk 
kk.c: In function 'main':
kk.c:10: warning: passing argument 2 of 'func' makes pointer from integer without a cast
$

Çağıran konumda (içinde main()) bir problem olduğunu gözlemleyeceksiniz - bir tamsayıyı, atamasız bir göstericiye dönüştürmek. function()Değerlerin ona nasıl aktarıldığını görmek için tüm kullanımlarında analiz etmeniz gerekecek . function()Aramalar yazılsaydı içimdeki kod çalışırdı :

unsigned long i = 0x2341;
function(1, &i);

Sizinki muhtemelen farklı yazıldığından, gösterilen değeri kullanmanın anlamlı olduğundan emin olmak için işlevin çağrıldığı noktaları gözden geçirmeniz gerekir. Unutmayın, gizli bir hata buluyor olabilirsiniz.

Ayrıca, void *parametrenin değerini (dönüştürüldüğü gibi) biçimlendirecekseniz , <inttypes.h>başlığa dikkatlice bakın (- yerine stdint.h-inttypes.hstdint.h alışılmadık olan hizmetlerini sağlar , ancak C99 standardı [t] başlığının başlığı <inttypes.h>içerdiğini <stdint.h>ve bunu barındırılan uygulamalar tarafından sağlanan ek olanaklarla genişletir ) ve biçim dizelerinizde PRIxxx makrolarını kullanın.

Ayrıca, yorumlarım kesinlikle C ++ yerine C'ye uygulanabilir, ancak kodunuz C ve C ++ arasında taşınabilir olan C ++ alt kümesinde. Yorumlarımın geçerli olma şansı oldukça iyi.


3
Sanırım sorumdaki noktayı kaçırdınız. Kod, bir işaretçide bir tamsayının değerini saklıyor. Ve kodun bu kısmı bunun tersini yapıyor (örneğin , bir işaretçi olarak yazılan tamsayının değerini çıkarmak ).
PierreBdR

@PierreBdR Yine de çok geçerli bir noktaya değiniyor. İşaretli bir int kullanan ancak bir boyut için kullanılan ve onu işaretsiz olarak değiştirmenin uygun olduğunu düşünen koda bakmak (derleyicilerin bu konuda uyardığı zamanlar dahil) her zaman o kadar basit değildir. Ne yazık ki her zaman bu kadar basit değil. Potansiyel hatalara ve bu konuda ince hatalara neden olmak istemediğiniz sürece her duruma açıkça bakmanız gerekir.
Pryftan

4
  1. #include <stdint.h>
  2. uintptr_tDahil edilen standart başlık dosyasında tanımlanan standart türü kullanın .

4

SQLite'ın kaynak kodunu incelerken bu soruyla karşılaştım .

Gelen sqliteInt.h , tamsayı ve olası arasında bir makro dönüştürmek tanımlanan kodu bir paragraf vardır. Yazar, önce derleyiciye bağlı bir sorun olması gerektiğine işaret eden çok iyi bir açıklama yaptı ve ardından çözümü oradaki popüler derleyicilerin çoğunu hesaba katmak için uyguladı.

#if defined(__PTRDIFF_TYPE__)  /* This case should work for GCC */
# define SQLITE_INT_TO_PTR(X)  ((void*)(__PTRDIFF_TYPE__)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(__PTRDIFF_TYPE__)(X))
#elif !defined(__GNUC__)       /* Works for compilers other than LLVM */
# define SQLITE_INT_TO_PTR(X)  ((void*)&((char*)0)[X])
# define SQLITE_PTR_TO_INT(X)  ((int)(((char*)X)-(char*)0))
#elif defined(HAVE_STDINT_H)   /* Use this case if we have ANSI headers */
# define SQLITE_INT_TO_PTR(X)  ((void*)(intptr_t)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(intptr_t)(X))
#else                          /* Generates a warning - but it always works     */
# define SQLITE_INT_TO_PTR(X)  ((void*)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(X))
#endif

Ve işte daha fazla ayrıntı için yorumun bir alıntı:

/*
** The following macros are used to cast pointers to integers and
** integers to pointers.  The way you do this varies from one compiler
** to the next, so we have developed the following set of #if statements
** to generate appropriate macros for a wide range of compilers.
**
** The correct "ANSI" way to do this is to use the intptr_t type.
** Unfortunately, that typedef is not available on all compilers, or
** if it is available, it requires an #include of specific headers
** that vary from one machine to the next.
**
** Ticket #3860:  The llvm-gcc-4.2 compiler from Apple chokes on
** the ((void*)&((char*)0)[X]) construct.  But MSVC chokes on ((void*)(X)).
** So we have to define the macros in different ways depending on the
** compiler.
*/

Kredi taahhüt edenlere gider.


2

Yapılacak en iyi şey, işaretçi türünden işaretçi olmayan türlere dönüştürmekten kaçınmaktır. Ancak, sizin durumunuzda bu kesinlikle mümkün değil.

Herkesin dediği gibi, uintptr_t kullanmanız gereken şeydir.

Bu bağlantı , 64 bit koda dönüştürme hakkında iyi bilgiler içerir.

Comp.std.c'de bununla ilgili iyi bir tartışma da var.


2

Bu durumda boşluk * "anlamının" genel bir tutamaç olduğunu düşünüyorum. Bir değere işaretçi değil, değerin kendisidir. (Bu sadece void * 'in C ve C ++ programcıları tarafından nasıl kullanıldığıdır.)

Bir tamsayı değeri tutuyorsa, tamsayı aralığında olması daha iyi!

İşte tamsayıya dönüştürme kolay:

int x = (char*)p - (char*)0;

Sadece bir uyarı vermelidir.


0

Yana uintptr_tolduğunu ++ / C ++ 11 C orada olması garanti edilmez bu düşünebilirsiniz tek yönlü dönüşüm ise, uintmax_ther zaman tanımlanmış <cstdint>.

auto real_param = reinterpret_cast<uintmax_t>(param);

Güvenli oynamak için, kodun herhangi bir yerine bir iddia eklenebilir:

static_assert(sizeof (uintmax_t) >= sizeof (void *) ,
              "No suitable integer type for conversion from pointer type");

Eğer uintptr_t'ye sahip değilseniz, o zaman uintmax_t da bir cevap değildir: Bir göstericinin değerini içinde saklayabileceğiniz kesin değildir! Bunu yapan bir tamsayı türü olmayabilir.
PierreBdR
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.