C programlamada void pointer kavramı


129

C programlama dilinde tip atama yapmadan bir boşluk göstericisinin referansını kaldırmak mümkün müdür?

Ayrıca, bir işaretçiyi alıp onu bir boşluk göstericisinde saklayabilen bir işlevi genellemenin herhangi bir yolu var mı ve bu boşluk işaretçisini kullanarak genelleştirilmiş bir işlev yapabilir miyiz?

örneğin:

void abc(void *a, int b)
{
   if(b==1)
      printf("%d",*(int*)a);     // If integer pointer is received
   else if(b==2)
      printf("%c",*(char*)a);     // If character pointer is received
   else if(b==3)
      printf("%f",*(float*)a);     // If float pointer is received
}

Bu işlevi if-else ifadelerini kullanmadan genel yapmak istiyorum - bu mümkün mü?

Ayrıca, boşluk gösterici kavramını açıklayan iyi internet makaleleri varsa, URL'leri sağlamanız faydalı olacaktır.

Ayrıca, boşluk işaretçileriyle işaretçi aritmetiği mümkün müdür?

Yanıtlar:


97

C programlama dilinde yazı dökümü yapmadan boş göstericinin referansını kaldırmak mümkün mü ...

Hayır, voidtürün yokluğunu gösterir, referansını kaldırabileceğiniz veya atayabileceğiniz bir şey değildir.

İşaretçiyi alıp boş göstericide saklayabilen bir işlevi genellemenin herhangi bir yolu var mı ve bu boşluk işaretçisini kullanarak genelleştirilmiş bir işlev yapabilir miyiz ..

Uygun şekilde hizalanmayabileceğinden, taşınabilir bir şekilde bundan kurtulamazsınız. ARM gibi bazı mimarilerde bir sorun olabilir, burada bir veri tipine işaretçi veri tipinin boyutunun sınırında hizalanmalıdır (örn. 32 bitlik tamsayıya işaretçi referans alınabilmesi için 4 baytlık sınırda hizalanmalıdır).

Örneğin, okuma uint16_tdan void*:

/* may receive wrong value if ptr is not 2-byte aligned */
uint16_t value = *(uint16_t*)ptr;
/* portable way of reading a little-endian value */
uint16_t value = *(uint8_t*)ptr
                | ((*((uint8_t*)ptr+1))<<8);

Ayrıca, boşluk işaretçileriyle işaretçi aritmetiği mümkündür ...

Göstericinin voidaltındaki somut değer ve dolayısıyla boyut eksikliğinden dolayı işaretçilerde işaretçi aritmetiği mümkün değildir .

void* p = ...
void *p2 = p + 1; /* what exactly is the size of void?? */

2
Yanlış hatırlamadığım sürece, bir boşluğa * iki şekilde güvenle başvurabilirsiniz. Char * 'a çevrim her zaman kabul edilebilirdir ve orijinal türü biliyorsanız, size işaret ederse o türe çevirebilirsiniz. Void * tür bilgisini kaybetti, bu yüzden başka bir yerde depolanması gerekiyordu.
Dan Olson

1
Evet, başka bir türe çevirirseniz her zaman referanstan vazgeçebilirsiniz, ancak yayın yapmazsanız aynısını yapamazsınız . Kısacası, derleyici, siz onu yayınlayana kadar matematik işlemleri için hangi montaj talimatlarını kullanacağını bilemez .
Zachary Hamm

20
Bence GCC, void *işaretçiler üzerindeki aritmetiği aynı şekilde ele alıyor char *, ancak bu standart değil ve ona güvenmemelisiniz.
ephemient

5
Takip ettiğimden emin değilim. Örneğin, yukarıda listelenen OP'nin, gelen türlerin, işlevin kullanıcısı tarafından doğru olduğu garanti edilir. Bir işaretçiye bilinen türde bir değer atadığımız bir şey varsa, onu bir boşluk işaretçisine atıp sonra hizasız hale gelebileceği orijinal işaretçi türüne geri attığımızı mı söylüyorsunuz? Derleyicinin, sırf onları bir boşluk işaretçisine attığın için rastgele hizalamayı neden bu kadar zayıflattığını anlamıyorum. Yoksa sadece kullanıcıya işleve aktardıkları argümanların türünü bilmesine güvenemeyeceğimizi mi söylüyorsunuz?
doliver

2
@doliver # 2'yi kastettim ("kullanıcının işleve aktardıkları argümanların türünü bilmesine güvenemeyiz"). Ama 6 yıl önceki cevabımı tekrar gözden geçirdiğimde (ugh, gerçekten bu kadar uzun mu oldu?) Ne demek istediğini anlayabiliyorum, bu her zaman böyle değil : orijinal işaretçi doğru tipe işaret ettiğinde, zaten hizalanmış olacaktı, yani Hizalama sorunları olmadan yayınlamak güvenli olacaktır.
Alex B

31

C'de, a void *, açık bir çevirme olmadan farklı türdeki bir nesneye bir işaretçiye dönüştürülebilir:

void abc(void *a, int b)
{
    int *test = a;
    /* ... */

Yine de bu, işlevinizi daha genel bir şekilde yazmanıza yardımcı olmaz.

void *Bir işaretçinin başvurusunu kaldırmak, işaret edilen nesnenin değerini elde etmek olduğundan, onu farklı bir işaretçi tipine dönüştürerek a'ya başvuruda bulunamazsınız . Çıplak voidbir tür geçerli bir tür değildir, bu nedenle a'nın ortadan kaldırılması void *mümkün değildir.

İşaretçi aritmetiği, işaretçi değerlerini sizeof, işaret edilen nesnelerin katları ile değiştirmekle ilgilidir . Yine, voidgerçek bir tür sizeof(void)olmadığı için anlamı yoktur , bu nedenle işaretçi aritmetiği geçerli değildir void *. (Bazı uygulamalar buna eşdeğer işaretçi aritmetiğini kullanarak izin verir char *.)


1
@CharlesBailey Kodunuza yazarsınız void abc(void *a, int b). Ama neden kullanmayalım void abc(int *a, int b), çünkü fonksiyonunuzda sonunda tanımlayacaksınız int *test = a;, bir değişkeni kaydedecek ve başka bir değişkeni tanımlamanın başka bir avantajı görmüyorum. void *Parametre olarak bu şekilde yazılmış birçok kod görüyorum ve daha sonra işlevde değişkenleri atıyorum . Ancak bu işlev yazar tarafından yazıldığından, değişkenin kullanımını bilmek zorundadır, öyleyse neden kullanmalı void *? Teşekkürler
Lion Lai

15

Java veya C # 'dan farklı olarak C'de bir void*işaretçinin işaret ettiği nesne türünü başarılı bir şekilde "tahmin etmenin" kesinlikle mümkün olmadığını bilmelisiniz . getClass()Basitçe benzer bir şey mevcut değildir, çünkü bu bilgi hiçbir yerde bulunamaz. Bu nedenle, aradığınız "jenerik" türü her zaman int b, örneğinizdeki veya printfişlevler ailesindeki biçim dizesi gibi açık meta bilgilerle birlikte gelir .


7

Bir void işaretçisi, herhangi bir veri türündeki değişkenlere başvurabilen genel işaretçi olarak bilinir.


6

Şimdiye kadar benim boş gösterici hakkındaki yorumum aşağıdaki gibidir.

Bir işaretçi değişkeni void anahtar sözcüğü kullanılarak bildirildiğinde - genel amaçlı bir işaretçi değişkeni haline gelir. Herhangi bir veri türündeki herhangi bir değişkenin adresi (char, int, float vb.) Bir void gösterici değişkenine atanabilir.

main()
{
    int *p;

    void *vp;

    vp=p;
} 

Diğer veri türü işaretçisi void işaretçisine atanabildiğinden, onu absolut_value (aşağıda gösterilen kod) işlevinde kullandım. Genel bir işlev yapmak için.

Argüman olarak tamsayı veya kayan sayı alan ve negatifse + ve yapmaya çalışan basit bir C kodu yazmaya çalıştım. Aşağıdaki kodu yazdım,

#include<stdio.h>

void absolute_value ( void *j) // works if used float, obviously it must work but thats not my interest here.
{
    if ( *j < 0 )
        *j = *j * (-1);

}

int main()
{
    int i = 40;
    float f = -40;
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    absolute_value(&i);
    absolute_value(&f);
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    return 0;
}   

Ama hata alıyordum, bu yüzden boşluk göstericiyle olan anlayışımın doğru olmadığını anladım :(. Şimdi puan toplamaya doğru ilerleyeceğim neden böyle?

Boşluk işaretçilerinde daha fazla anlamam gereken şeyler bu.

Bunu kaldırmak için void işaretçi değişkenini yazmamız gerekir. Bunun nedeni, bir geçersiz göstericinin kendisiyle ilişkilendirilmiş hiçbir veri türüne sahip olmamasıdır. Derleyicinin, geçersiz gösterici tarafından hangi tür verilere işaret edildiğini bilmesinin (veya tahmin etmesini) hiçbir yolu yoktur. Dolayısıyla, bir boşluk gösterici tarafından işaret edilen veriyi almak için, onu boşluk işaretçileri konumunda tutulan doğru veri türü ile yazıyoruz.

void main()

{

    int a=10;

    float b=35.75;

    void *ptr; // Declaring a void pointer

    ptr=&a; // Assigning address of integer to void pointer.

    printf("The value of integer variable is= %d",*( (int*) ptr) );// (int*)ptr - is used for type casting. Where as *((int*)ptr) dereferences the typecasted void pointer variable.

    ptr=&b; // Assigning address of float to void pointer.

    printf("The value of float variable is= %f",*( (float*) ptr) );

}

Programcı, son kullanıcı tarafından girilen veri tipinden emin değilse, bir boşluk gösterici gerçekten yararlı olabilir. Böyle bir durumda programcı, bilinmeyen veri tipinin konumuna işaret etmek için bir boş gösterici kullanabilir. Program, kullanıcıdan veri tipini bildirmesini isteyecek şekilde ayarlanabilir ve kullanıcı tarafından girilen bilgilere göre tip ataması yapılabilir. Aşağıda bir kod parçası verilmiştir.

void funct(void *a, int z)
{
    if(z==1)
    printf("%d",*(int*)a); // If user inputs 1, then he means the data is an integer and type casting is done accordingly.
    else if(z==2)
    printf("%c",*(char*)a); // Typecasting for character pointer.
    else if(z==3)
    printf("%f",*(float*)a); // Typecasting for float pointer
}

Boşluk işaretçileriyle ilgili aklınızda bulundurmanız gereken bir diğer önemli nokta da - işaretçi aritmetiği bir boşluk göstericide gerçekleştirilemez.

void *ptr;

int a;

ptr=&a;

ptr++; // This statement is invalid and will result in an error because 'ptr' is a void pointer variable.

Artık hatamın ne olduğunu anladım. Ben de aynısını düzeltiyorum.

Referanslar :

http://www.antoarts.com/void-pointers-in-c/

http://www.circuitstoday.com/void-pointers-in-c .

Yeni kod aşağıda gösterildiği gibidir.


#include<stdio.h>
#define INT 1
#define FLOAT 2

void absolute_value ( void *j, int *n)
{
    if ( *n == INT) {
        if ( *((int*)j) < 0 )
            *((int*)j) = *((int*)j) * (-1);
    }
    if ( *n == FLOAT ) {
        if ( *((float*)j) < 0 )
            *((float*)j) = *((float*)j) * (-1);
    }
}


int main()
{
    int i = 0,n=0;
    float f = 0;
    printf("Press 1 to enter integer or 2 got float then enter the value to get absolute value\n");
    scanf("%d",&n);
    printf("\n");
    if( n == 1) {
        scanf("%d",&i);
        printf("value entered before absolute function exec = %d \n",i);
        absolute_value(&i,&n);
        printf("value entered after absolute function exec = %d \n",i);
    }
    if( n == 2) {
        scanf("%f",&f);
        printf("value entered before absolute function exec = %f \n",f);
        absolute_value(&f,&n);
        printf("value entered after absolute function exec = %f \n",f);
    }
    else
    printf("unknown entry try again\n");
    return 0;
}   

Teşekkür ederim,


2

Hayır, bu mümkün değil. Başvurulan değerin türü ne olmalıdır?


2
void abc(void *a, int b) {
  char *format[] = {"%d", "%c", "%f"};
  printf(format[b-1], a);
}

Bu program mümkün mü .... Lütfen kontrol edin, eğer bu mümkünse C programlamada ...
AGeek

2
Evet, bu mümkündür (b için bir aralık kontrolü olmaksızın kod tehlikelidir). Printf değişken sayıda argüman alır ve a sadece herhangi bir işaretçi olarak (herhangi bir tür bilgisi olmadan) yığına itilir ve format dizesindeki bilgileri kullanarak va_arg makrolarını kullanarak printf fonksiyonunun içine atılır.
hlovdal

@SiegeX: Lütfen neyin taşınabilir olmadığını ve neyin tanımsız olabileceğini açıklayın.
Gauthier

@Gauthier, biçim belirleyicileri içeren bir değişkeni iletmek taşınabilir değildir ve potansiyel olarak tanımlanmamış bir davranış. Böyle bir şey yapmak istiyorsanız, 'vs' çeşidine bakın printf. Bu cevabın buna güzel bir örneği var.
SiegeX

Uygulamada, muhtemelen int bbir enum ile değiştirirdim , ancak bu numaralandırmada daha sonra herhangi bir değişiklik yapmak bu işlevi bozacaktır.
Jack Stout

1

Bu işlevi ifs kullanmadan genel yapmak istiyorum; mümkün mü?

Gördüğüm tek basit yol, AFAIK C programlama dilinde bulunmayan aşırı yüklemeyi kullanmaktır.

Programınız için C ++ programlama dilini düşündünüz mü? Veya kullanımını yasaklayan herhangi bir kısıtlama var mı?


Tamam, bu utanç verici. Benim açımdan o zaman hiçbir çözüm göremiyorum. Aslında, printf () & cout ile aynıdır: yazdırmanın 2 farklı yolu. printf () if () deyimini, biçim dizesini çözmek için kullanın (sanırım veya benzer bir şey), cout durum operatörü için << aşırı yüklenmiş bir işlevdir
yves Baumes

1

İşte işaretçiler hakkında kısa bir voidişaretçi: https://www.learncpp.com/cpp-tutorial/613-void-pointers/

6.13 - Geçersiz işaretçiler

Boş gösterici ne tür bir nesneye işaret ettiğini bilmediğinden, doğrudan referans alınamaz! Bunun yerine, geçersiz göstericinin başvurusu kaldırılmadan önce açıkça başka bir işaretçi türüne dönüştürülmesi gerekir.

Bir boşluk işaretçisi neye işaret ettiğini bilmiyorsa, onu neye çevireceğimizi nasıl bileceğiz? Sonuçta, bunu takip etmek size kalmış.

Geçersiz işaretçi çeşitli

Boş gösterici üzerinde işaretçi aritmetiği yapmak mümkün değildir. Bunun nedeni, işaretçi aritmetiğinin, işaretçinin hangi boyuttaki nesneyi işaret ettiğini bilmesini gerektirmesidir, böylece işaretçiyi uygun şekilde artırabilir veya azaltabilir.

Makinenin belleğinin bayt-adreslenebilir olduğunu ve hizalı erişim gerektirmediğini varsayarsak, a'yı yorumlamanın en genel ve atomik (makine seviyesinde gösterime en yakın) yolu, void*bir bayta işaretçi gibidir uint8_t*. Bir Döküm void*a uint8_t*, örneğin, ilk 1/2/4/8 / Ancak-birçok-sen-arzu söz konusu adreste başlayan bayt yazdırmak izin verecek, ancak çok başka yapamaz.

uint8_t* byte_p = (uint8_t*)p;
for (uint8_t* i = byte_p; i < byte_p + 8; i++) {
  printf("%x ",*i);
}

0

Boş bir yazıcıyı kolayca yazdırabilirsiniz

int p=15;
void *q;
q=&p;
printf("%d",*((int*)q));

1
Kimin voidaynı büyüklükte olduğunu garanti ediyor int?
Ant_222

Bu teknik olarak bir voidişaretçi yazdırmak değildir int, işaretçinin içerdiği bellek adresine (bu durumda değeri olacaktır p) yazdırmaktır .
Marionumber1

0

C statik olarak yazılmış, türü kesin belirlenmiş bir dil olduğundan, derlemeden önce değişken türüne karar vermelisiniz. C'de jenerikleri taklit etmeye çalıştığınızda, sonunda C ++ 'yı yeniden yazma girişiminde bulunacaksınız, bu nedenle bunun yerine C ++ kullanmak daha iyi olacaktır.


0

void gösterici genel bir göstericidir .. Herhangi bir değişkenin herhangi bir veri türünün adresi bir void işaretçisine atanabilir.

int a = 10;
float b = 3.14;
void *ptr;
ptr = &a;
printf( "data is %d " , *((int *)ptr)); 
//(int *)ptr used for typecasting dereferencing as int
ptr = &b;
printf( "data is %f " , *((float *)ptr));
//(float *)ptr used for typecasting dereferencing as float

0

Tipini belirtmeden bir göstericiye başvuruda bulunamazsınız çünkü farklı veri türleri bellekte farklı boyutlara sahip olacaktır, yani int 4 bayt, karakter 1 bayttır.


-1

Temel olarak, C'de "türler" bellekteki baytları yorumlamanın bir yoludur. Örneğin, aşağıdaki kod ne

struct Point {
  int x;
  int y;
};

int main() {
  struct Point p;
  p.x = 0;
  p.y = 0;
}

"Main'i çalıştırdığımda, 4 (tamsayı boyutu) + 4 (tam sayı boyutu) = 8 (toplam bayt) bellek ayırmak istiyorum." .X '' i tür etiketli bir değere ldeğer olarak yazdığımda Derleme zamanında gelin, göstericinin bellek konumundan dört bayt artı veri alın. Dönüş değerine derleme zamanı etiketi "int" "verin.

Çalışma zamanında bilgisayarın içinde "Point" yapınız şu şekilde görünür:

00000000 00000000 00000000 00000000 00000000 00000000 00000000

Ve burada ne void*(32 bit bilgisayar varsayarak): veri türü gibi görünebilir

10001010 11111001 00010010 11000101

Bu soruya cevap vermiyor
MM

-2

Bu işe yaramaz, yine de void *, genel göstericiyi işlevlere tanımlamada ve onu başka bir işleve (Java'daki geri çağrıya benzer) bir argüman olarak aktarmada çok yardımcı olabilir veya oop'a benzer bir yapı tanımlayabilir.

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.