C char** Pointer

Merhabalar, C ile bir şeyler yapmaya çalışıyorum. Aşağıdaki fonksiyonun sonucunda oluşan değerleri bir char pointera referans edip o değere oradan erişmek istediğimde rastgele değerler alıyorum.

run/server.h

#include <stdio.h>
#include <windows.h>
#include <winhttp.h>

int get_version_from_server(int timeout, BOOL SSLVerify, char** output_pointer){
    HINTERNET winhttp = WinHttpOpen(L"Test", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);

    if(!winhttp){
        return GetLastError();
    }

    HINTERNET server_connection = WinHttpConnect(winhttp, L"domain", INTERNET_DEFAULT_PORT, 0);

    if(!server_connection){
        return GetLastError();
    }

    HINTERNET server_request = WinHttpOpenRequest(server_connection, L"GET", L"url", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);

    if(!server_request){
        return GetLastError();
    }

    BOOL set_timeout = WinHttpSetTimeouts(server_request, timeout*1000, timeout*1000, timeout*1000, timeout*1000);

    if(!set_timeout){
        return GetLastError();
    }


    BOOL request_response = WinHttpSendRequest(server_request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, NULL, 0, 0, 0);

    if(!request_response){
        return GetLastError();
    }

    request_response = WinHttpReceiveResponse(server_request, NULL);

    if(!request_response){
        return GetLastError();
    }

    unsigned long int data_size;

    BOOL get_available_data_size = WinHttpQueryDataAvailable(server_request, &data_size);

    if(!get_available_data_size){
        return GetLastError();
    }

    char data[data_size+1];

    BOOL read_data = WinHttpReadData(server_request, &data, data_size, &data_size);

    if(!read_data){
        return GetLastError();
    }

    *output_pointer = &data[0];

    return 0;

}

main.c

#include <stdio.h>
#include <string.h>
#include <windows.h>
#include "run/file.h"
#include "run/server.h"

int main(void){
    char* version_data;

    get_version_from_server(3, TRUE, &version_data);

    printf("Version string: %s", version_data);
}

Bu şekilde denediğimde rastgele bir dizi değerler alıyorum. Ancak şöyle bir şey denediğimde:

test.c

#include <stdio.h>

int test(char** ptr){
    char a[1025] = "test";
    
    *ptr = &a[0];

    return 0;

}


int main(void){
    char* ptr;

    test(&ptr);

    printf("Sonuc: %s", ptr);
}

bir sıkıntı oluşmuyor ve doğru sonucu görüyorum. Bu iki kod arasındaki tam fark ne ve nasıl ilk fonksiyonda pointerı doğru bir şekilde referans edebilirim?

Cunku ilk degeri rastgele ve fonksiyon degeri degistirmiyor.

Burada sikinti var; fonksiyon dondugunde a’nin omru bitmis oldugu icin ptr yanlis yere isaret ediyor.

Tam olarak anlayamadım. Biraz daha detaylı açıklayabilir misiniz? Değişkenin ömrünün bitmesi ne demek mesela?

Sadece programın sonucuna bağlı olarak konuşmuştum, program doğru değeri göstermişti. Acaba genel standartlara göre mi bir sıkıntı var?

Degiskenin sahip oldugu lifetime’in disindayiz demek (teknik terim). auto degiskenlerin scope’lari bittiginde lifetime’lari da biter, onlari gosteren pointer’lar gecersiz hale gelir ve dereference edilmemelidir.

Burada bir undefined behavior var. Undefined behavior’un olasi sonuclarindan bir tanesi programin dogru degeri gostermesi malesef.

a gibi baska bir array tanimlayan bir fonksiyonu cagirirsaniz ptr’nin gosterdigi degerin degistigini gorebilirsiniz mesela. (Buyuk ihtimalle. Her sey olabilir; U.B.'nin dogasi geregi.)

2 Beğeni

Anladığım kadarıyla değişkene scope dışından erişemiyoruz. Burada ömrünün sonlanmasından kasıt nedir, değişkenin silinmesi mi? O zaman pointerları bu şekilde kullanamıyor muyuz?

Tam olarak undefined behavior nedir? Eğer scope dışında değişken sonlanıyorsa nasıl doğru sonuç veriyor? Açıklayabilir misiniz?

Asıl soruma gelirsek, peki scope içerisindeki bir değişkene nasıl bu değişkeni yeni değişkene kopyalamadan erişebilirim?

Merhaba;

Burada bir fonksiyona &version_data ile giriş yaptınız.

Sonra fonksiyon görevini yaptı.

*output_pointer = &data[0];

ile istediğiniz adres yüklendi.

ama siz version_data’ya bir şey yüklemediniz.

Yani ;

char* version_data;

İlk tanımlandığında derleyicinize göre ya rastgele bir bellek adresine işaret ediyor yada null veya 0 gibi bir değere işaret ediyor.

Fonksiyona bu rastgele değeri girdi olarak veriyoruz ama fonksiyon bu değere hiç bir değişiklik yapmıyor.

Fonksiyon sadece

*output_pointer = &data[0];

değerini değiştiriyor.

Ama siz bunu yazdırmıyorsunuz. Başta belirttiğim ilk olarak rastgele bir belleğe işaret eden değişkeni yazdırıyorsunuz.

Özetle fonksiyona işaretçi girdisi vermekle fonksiyondan aynı işaretçinin kendiliğinden geri dönmesini beklemek hatalı bir yaklaşım.

global ve extern anahtarlarına bakabilirsiniz.

Yada return ile doğrudan değişkeni döndürebilirsiniz.

Return ile değeri geri çağırmak en basiti, bir fonksiyonun girdileri ve çıktısı olan temiz bir kod olur.

Kendim de test ettiğim zaman output_pointer’ın fonksiyon içerisinde doğru adresi gösterdiğini version datanın ise alakasız bir değer gösterdiğini fark etmiştim. Yani fonksiyon argüman olarak gönderilen pointera bir şey yapmayıp yeni bir pointer oluşturup ona adres yazıyor. Doğru muyum? Aslında bu şekilde kullanmaya çalışmamın sebebi fonksiyonun hata verdiği zaman oluşan hatayı alabilmekti. Peki char döndüren bir fonksiyondan nasıl int türünden hata kodu döndürebilirim? Tavsiyeniz nedir? Aklıma bir struct oluşturmak geliyor ama aklıma daha tam oturmadı.

char* ptr;

tanımlamışsınız main de;

*ptr = &a[0];

sonra tanımladığınız ptr yi yüklemişsiniz sorun yok.

İlkinde;

char* version_data;

Tanımlamışsınız ama

*output_pointer = &data[0];

yüklemişsiniz.

Sorun bundan ibaret.

Fonksiyona hata mesajı döndürüp, alacağınız değerleri global bir değişkenle alabilirsiniz.

Kod denemeden pek tavsiye vermem ama;

Yerine

`*version_data = &data[0];`

yüklemeniz yeterli gibi. Pointer to poiner durumlarına dikkat etmedim ama version_data global tanımlı ve kod sounda siz o değeri bu değişkene atarsanız.

Yazdırırken sorun yaşamazsınız sanırım.

Pek buna gerek yok ve olağan bir durum değil ama, zorunlu ise;

chat döndüren bir fonksiyona hata mesajını char a çevir deyip yükler.

char çıkan sonucu da tekrar int e çevirirdim. Sonuçta 255 e kadar olan hata kodlarını bu şekilde belki çekebilirsiniz. Char da sonuçta aslında bir sayıyı simgeliyor.

Sonuçta yapılabilecek çok yöntem var.

Maalesef gene aynı sorunu yaşadım. Sizin dediğiniz gibi fonksiyonu char olarak döndürmeyi deneyeceğim.

Visual Studio Community 2022 edition:

server.h:

#include <stdio.h>
#include <Windows.h>
#include <winhttp.h>

#pragma comment(lib, "winhttp.lib")


int get_version_from_server(int timeout, BOOL SSLVerify, char** version_data) {
   
    DWORD dwSize = 0;
    DWORD dwDownloaded = 0;
    LPSTR pszOutBuffer;
    BOOL  bResults = FALSE;

    HINTERNET winhttp = WinHttpOpen(L"test", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
        
    HINTERNET server_connection = WinHttpConnect(winhttp, L"www.google.com", INTERNET_DEFAULT_PORT, 0);

    HINTERNET server_request = WinHttpOpenRequest(server_connection, L"GET", L"url", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);

    BOOL set_timeout = WinHttpSetTimeouts(server_request, timeout * 1000, timeout * 1000, timeout * 1000, timeout * 1000);

    BOOL request_response = WinHttpSendRequest(server_request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, NULL, 0, 0, 0);

    request_response = WinHttpReceiveResponse(server_request, NULL);

    BOOL get_available_data_size = WinHttpQueryDataAvailable(server_request, &dwSize);


    pszOutBuffer = new char[dwSize+1];

    ZeroMemory(pszOutBuffer, dwSize + 1);

    BOOL read_data = WinHttpReadData(server_request, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded);

   *version_data = &pszOutBuffer[0];

    return 0;

}

main.c:

#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <winhttp.h>

#include "server.h"



int main(void) {
    
    char* version_data;

    get_version_from_server(3, TRUE, &version_data);

    printf("Version string: %s", version_data);

Ben server.h dosyasını main ile aynı klasöre koydum. Siz kendinize göre ayarlarsınız.

sizin hata yakalama satırlarını kaldırdım. Geçici olarak siz geri eklersiniz.

google’a request attım.

Sonuç:

Sorunuz olursa tekrar ilgilenmeye çalışırım.

Burada version olarak neye baktığınızı bilemedim.

Onu version_data dan yada pszOutBuffer dan alabileceğinizi düşünüyorum.

Anladım ancak biraz kafam karıştı. Sanki dediğiniz kodda argüman olarak gelen pointerı değil de global pointerı değiştiriyormuşuz gibi geldi doğru mu bilemiyorum. Benim anladığım şöyle:

def test(pointer):
    global pointer

    pointer = 10

pointer = 0
test(pointer)

Aslında bir program için installer/updater türü bir uygulama geliştiriyordum. Daha tamamlamadım ancak ilk önce kullanacağım fonksiyonları yazmaya karar vermiştim. Windowsta native olarak winhttp bulduğum için winhttp kullanmaya karar verdim doğru bir karar mı bilmiyorum (C’ye pek hakim olmadığım için.). Eğer kütüphane hakkında da tavsiyeniz varsa dinlerim.

Evet aslında main içindeki pointer’ı doğrudan yükleyebilirsiniz. Global bir pointer zaten.

Fonksiyona geçtiğinizde yükleyememişsiniz. Yada hatalı yüklemişsiniz.

Düzelttiğim ve verdiğim kodu kullandığınızda,

*output_pointer = &pszOutBuffer[0];

şeklinde de kullanabilirsiniz.

önerimde çalışmadığını söylediğinizde, aslında yerel değişkenler yerine doğrudan yüklemeniz çalışmalıydı. Çalışmadığını söylediğinizde sorun işaretçiye (pointer) veri yüklenemediğinden gibi geldi.

Neden yüklenememiş olabilir, &data için değişken ataması yada api çağrısı hatası olabilirdi.

Ben de sizin kodu yeniden yazdım. Denediğinizde ister global, ister yerel işaretçiye işaretçi işe yarayacaktır.

Yani kafanız karışmasın. Parametre geçerken işaretçi tanımlarken oluşan bir sorun değil. Asıl sorun. Data’yı çekememişsiniz.

Ben de Microsoft MSDN referansına göre kodu düzeltim.

Siz benim verdiğim kodu kullanmayı denerseniz değişkenleri ister global ister yerelden gönderdiğinizde aynı adrese ulaştığından sorun yaşamayacaktır.

Aslında burada webhttp de yol katetmişsiniz. Bu nedenle farklı bir api önermek doğru olur mu bilemedim.

Güncelleme kontrolü için http kullanmak zorunda değilsiniz bu sizin güncelleme tasarımına bağlı. winsocket ile doğrudan bağlanıp, istediğiniz porttan istediğiniz formatta veri alışverişi yapabilirsiniz.

Burada siz html üzerinden haberleşmeyi seçmişsiniz. Size kalmış.

Gözünüzde büyümesin winapi gayet kolaydır. Tek tavsiyem, eğer win api kullanacaksanız, değişkenlerinizi oluştururken MSDN üzerindeki değişken isimlerini birebir kullanmak.Böylece hangi değişken hangisiydi karışmaz.

Özetle.

Konsol çıktısı verdiğim kodu inceleyin çalıştırın. Orada ister global işaretçiyi ister fonksiyon içerisinde kendi tanımlayacağınız işaretçiye işaretçiyi kullanın çalışacaktır. Ben hem global hem yerel olanı aynı isimle tanımladım kafanız karışmasın diye. O da karıştırırsa siz değiştirebilirsiniz.

Kodu derlediğimde karşılaştığım derleme hatalarını da giderdim açıkcası sizin kod doğrudan ben de çalışmadı hangi derleyicide derleyebildiniz bilmiyorum. Ama sorun sizin kodunuzda pointerlardan değil, pointer a yüklemeye çalıştığınız değerleri üreten api kodlarındaymış.

Updater için farklı dillere ve farklı metodlara ayrıca bakabilirsiniz. Ben her api ile bir serverdan data çekilebileceğini düşündüğümden özellikle birini tavsiye etmem, hangisini beğeniyorsanız onu kullanın.

Gcc’de -lwinhttp argümanıyla derlemiştim. Bende derlediğimde hata oluşmuyordu.

Aslında win apiyi zor bulmuyorum. Hatta installer’ı C ile yazmaya iten şey win api’nin kolay görünüşüydü diyebilirim. Anladığım kadarıyla winhttp ile yaptığım istekte hata olduğunu, bundan dolayı gelen verinin bozuk olduğunu ve pointerın doğru çalıştığını söylemişsiniz. Eğer öyleyse, fonksiyon içerisinde data arrayını yazdırdığımda doğru sonucu görmüştüm. Bundan dolayı pointerla ilgili bir sorun diye düşünmüştüm. Siz hangi değişikliği yaparak düzgün çalışmasını sağladınız?

Evet.

pszOutBuffer = new char[dwSize+1];

    ZeroMemory(pszOutBuffer, dwSize + 1);

    BOOL read_data = WinHttpReadData(server_request, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded);

gcc kullandığınız için gcc daha kurallara bağlı bir derleyicidir. C kodu ise C, c++ kodu ise c++ muamelesi yapar koda.

Visual Studio da tarzanca C ve C++ birlikte yazabilirsiniz.

Normalde,

 pszOutBuffer = new char[dwSize+1];

new anahtar kelimesi c++ a aittir ve c derleycilerinde geçersizdir.
Ama microsft ilginç bir şekilde örnek kodların da kullanıyor. Linkin altındaki örnek kodu inceleyebilirsiniz.

WinHttpReadData function (winhttp.h) - Win32 apps | Microsoft Learn

Evet, ben de fark ettim. Tavsiyeniz için teşekkürler. Visual Studio’yu da deneyeceğim.

Eğer dediğiniz gibiyse, fonksiyon içinde konsola yazdırılan arrayın da hatalı olması gerekmez mi? Bu nasıl mümkün oluyor o zaman?

Aslında kodun çoğunluğunu dokümantasyonu rakip ederek yazmıştım, gözümden bir şey kaçmış belli ki. Tavsiyeniz için teşekkürler.

char data[data_size+1];
char*  data[data_size+1];

Gibi bir tanımlamada düzeltme ile giderilebilir. Kodu çalıştıramadığımdan deneyemiyorum.

Yani buffer ı yanlış ayırmış da olabilirsiniz.

Teşekkürler, müsait olduğumda denerim. Ancak hatırladığım kadarıyla gcc derlerken türlerle ilgili uyarı veriyordu diye hatırlıyorum.

Evet, scope’un tanimi degiskene erisilebilen yerler silsilesi.

Burada silinmekten kasit nedir?

Teknik olarak tam olarak soyledigim gibi… Lifetime’i biten degiskenlerin adresleri invalide olur, pointer’lari dereference edersek UB olur.

Konusma dili olarak… Evet, “silinmis” gibi dusunebiliriz.

Hayir.

UB gayet arastirilabilir bir terim. Sonrasinda anlamadiginiz bir sey olursa tekrar sorunuz.

Silinmis bir dosyayi nasil bazen geri getirebiliyoruz? Ayni mekanizma.

Scope icindeki bir degiskene ismiyle erisebilirsiniz (shadow edilmiyorsa), bu scope’un tanimi neredeyse.

Scope disindan erismek icin pointer dondurebilrisiniz. Ama lifetime’i uzatmaniz lazim. static kullanabilirsiniz mesela. Veya malloc.

Fonksiyon hata donduruyor.

Bu satir calismiyor.

Fonksiyona bu pointer’in adresini veriyoruz.

C’de bir fonksiyondan veri dondurmenin iki yolu return etmek ve veriye pointer alip gosterdigi yere yazmak. Burada return degeri hata durumu icin kullanildigi icin ikinci yontem kullanilmis. Gayet standart bir sey.

Global degisken kullanmak yukarida bahsettigim iki yonteme bir alternatif (baskalari da var) ama siddetle karsi cikilan bir alternatif.

extern keyword’u linkage belirler, konumuzla bir alakasi yok.

Dogru. Bunlarin farkli turler oldugunu da unutmayalim. char **, char * dondurmek icin kullanilabilecek bir mekanizma.

Tekrarlamaktan yoruldugum gibi, problem fonksiyonun output_pointer’a dokunmamasi. Fonksiyon calismiyor.

Initialize edilmemis bir pointer dereference ediliyor burada. Buyuk sorun var.

Oy oy oy oy oy

Fonksiyon gayet iyiydi (GetLastError’in 0 dondurdugu durumlar disinda), sorun main’in return degerini kontrol etmemesi idi.

Karsiligi delete olmadigi icin memory leak ediyor.

Bu arada bu kodun orijinal koddan tek farki hata durumlarini es gecmesi ve version data ciktisini her zaman vermesi. Orijinal kod sadece her sey basarili ise cikti veriyor.

1 Beğeni

Anladım. Yani değişkenin değerine sırf adresini bildiğimiz için erişebiliyoruz. Ancak tabi bu değişken kullanılamaz halde olduğu için adresteki veriler korunmuyor ve overwrite edilebiliyor.

Dediğinizi anladım. Fonksiyon argüman pointera dokunmak yerine scope içinde yeni bir pointer oluşturuyor yani.

Peki bu dediğinizi nasıl yapabilirim? Bir örnek verir misiniz?

Bu arada herhangi biri “peki HTTP request yapan fonksiyondan disari data nasil dondurebilirim” sorusunu soracak olursa:

Fonksiyondan daha uzun omurlu bir hafiza alanina ihtiyac var. static char foo[1024]; bir opsiyon, fakat fonksiyonun reentrant ve thread-unsafe olmasini garanti ediyor. (Cunku ikinci cagrilmada ilk datanin uzerine yazilacak.)

Her cagriya ozel olarak malloc ile dinamik hafiza alani almak en mantikli opsiyon olabilir. Dondurulen pointer’in free edilmesi gerektigini cagiran tarafa belirtmek gerek, yoksa memory leak olur.

C++'ta malloc+free yerini new[]+delete[]'e birakiyor.

Baska bir opsiyon da cagiran taraftan hafiza alani acmasini istemek. Bu alani ve boyutunu parametre olarak alip, alanin ne kadar kullanildigini dondurmek lazim. Bu ikisi genelde tek bir size_t* degeriyle yapiliyor. (Giriste alanin buyuklugune isaret ediyor, cikista kullanilan alan boyuna.) Yukaridakinin malloc'u cagiran tarafin yaptigi versiyonu.

1 Beğeni