Pointer (ukazatel) v "C"- základy práce s pointery

Prosím o radu s následujícími problémy při práci s pointry, který je snad patrný z tohoto kódu:

char *str=“123456789123456789”;
int *ij=111;
void main()
{
*ij= 222 // OK
ij=222 // nic se nestane

//ale

*str=“a”; // pøiøadí se nesmysl
str =“a”; // pøiøazení je OK
}

Díky za rady a tipy

:arrow_right: administrator: přejmenováno z "pointer v “C”"

Práce s pointrama (ukazatelema) je v Céčku docela významná - obzvlášť, když pracuješ s textama. Taktéž předávání proměnných (nikoliv hodnot) do funkcí se provádí pomocí pointrů.

Ukazatele slouží k tomu, že ukazují na nějaké místo v paměti. S ukazateli se takto (*ij=222) pracuje pouze tehdy, že vytvoříš nějakou proměnnou: ij=(int*)malloc(sizeof(int);
Pak ukazatel ij ukazuje na místo *ij, kde je alokované místo pro int.

Používá se například při předávání parametr funkcím, pokud chceš, aby funkce změnila hodnotu nějaké proměnné. Práce s řetězci je vlastně jenom práce s pointry.

Jaký je rozdíl mezi těmito funkcemi, jejich voláním a návratovými hodnotami :

[code]int cislo1, cislo2;
prehod cisla(cislo1, cislo2);

void prehod cisla(int A, int B)
{
int pomocna;

pomocna=A;
A=B;
B=pomocna;
}
[/code]

[code]int cislo1, cislo2;
prehod cisla(&cislo1, &cislo2);

void prehod cisla(int *A, int *B)
{
int pomocna;

pomocna=*A;
*A=*B;
*B=pomocna;
}
[/code]

V prvním případě předáváš funkci číslo - tedy hodnotu proměnných cislo1 a cislo2. Funkce je sice v lokálních proměnných A a B přehodí, ale vlastně navenek neudělá vůbec nic.

Ve druhém případě předáš funkci adresy proměnných cislo1 a cislo2. Funkce převezme jejich adresy do lokálních pointrů *A a *B. Hodnotu (obsah) z adresy A přepíše do pomocné proměnné pomocna, hodnotu s adresy B přiřadí hodnotě na adrese A a pomocnou proměnnou uloží na adresu B. Tím, že funkce dostala jako parametr adresu proměnné a nikoliv hodnotu, tak de-facto pracovala s proměnnými cislo1 a cislo2. Jinýmu slovy - skutěčně tyto hodnoty přehodila.

Nezapomeň, že pokud vytvoříš pointer, tak vytvoříš POUZE ukazatel na nějaké paměťové místo. Musíš pro hodnotu proměnné vytvořit a to buď tím, že alokuješ odpovídající paměť nebo tím, že ukážeš na místo, kde nějaké poměnná je a pomocí tohoto ukazatele s ní pracuješ. Tak, jak jsi to napsal je to špatně. Tuhle deklaraci

int *ij=111; některé překladače ani nezkompilují, protože je špatně. Nelze přiřadit (int *)=(int);

Doporučuju Ti vyhledat si pojem “Práce s pointry” a prostudovat si to. Práce s nimi není až tak složitá, jak se na první pohled zdá. Jak říkával náš učitel C - jsou mocné, ale také nebezpečné. A to z toho důvodu, že když jsi neopatrný, můžeš sahnout prakticky kamkoliv do dat a nadělat si pěknou paseku.

Mně u pointerů přijde zřejmější, když se hvězdička píše za typ a ne před jméno proměnné - pak mi to jasněji říká, že to je ukazatel na ten datový typ (nelze takto deklarovat více proměnných najednou).

Např.: int i … i je proměnná typu int. Ale: int* p … p je ukazatel na proměnnou (nebo číslo) typu int. Pak mohu zapsat:
int i = 111; // do int proměnné i uložím číslo 111
int* p; // to je deklarace ukazatele na číslo typu int
p = &i; // takto si do proměnné p uložím ukazatel na proměnnou i
int j = *p; // tato operace uloží do proměnné j číslo z místa, na které ukazuje ukazatel p, tj. uloží tam to 111.
*p = 222; // tímto se uloží do proměnné i číslo 222 (použije se ukazatel na ně)

Trochu mohou být matoucí texty - v C se s texty pracuje pomocí ukazatelů na textový řetězec. Proto zápis char* str=“1234” uloží do proměnné str ukazatel na text “1234”, který má program někde v paměti datových konstant. Neuloží tam tedy přímo ten text. Do proměnné lze pak uložit ukazatel na jiný text zápisem str=“abcd”. Změní se jen ukazatel v proměnné str. Původní textový řetězec “1234” tam někde v paměti konstant zůstal, ale už k němu nemáme přístup.

Jenže se můžou deklarovat proměnné i takto :

int *p, i;

Pak zápis

int* p, i;

je docela hodně matoucí, protože ve výsledku jsou oba shodné.

Přesnější popis by asi byl : takto si do ukazatele p uložím adresu proměnné i.

Jinak Panda popsal ukazatele perfektně.

Pánové,
děkuji vám oběma za odpovědi, nicméně se vám podařilo uvařit v mé hlavě ještě větší guláš než jsem měl.
Dokumentaci jsem pochopitelně prostudoval - jednu českou a jednu originál k překladači Mikro C od fy Mikroelektronika. Obě dokumentace se schodují na zápisu

char *str=“123456789123456789”;
int *ij=111;

a překladač to bez chyb přeloží. U int pointru

int *ij=111; přiřazení *ij= 222; funguje tak jak je psáno v dokumentaci. Pro znakový pointer přiřazení *str=“a”; nefunguje a musím použít zápis
str= “a”;. Přitom pri definici a inicializaci pointeru
char *str=“1122334” ; to funguje. A to je to divné, nemyslíte?

Dle teorie *str je ukazatel na konkrétní část paměti str by měl být obsah který je do paměti uložen. Dále jsem vyrozuměl, že příkaz
*st=’‘a’";
uloží na adresu danou pointerem *str hodnotu “a”.
Zřejmě to chápu špatně, nebo si každý překladač s různými poinery pracuje po svém.
Bohužel, vaše odpovědi mně rozhřešení neposkytly.
Přesto díky za snahu.

Ano to jsem měl na mysli tím, že nelze takto deklarovat více ukazatelů najednou (int* p,i nemá význam dvou ukazatelů). Mně totiž stále není jasné, jak si představit zápis int p. Chápal bych zápis int p jako že to je proměnná p, která je typu ukazatel na int. Na ten druhý zápis nevím jak se dívat. Proto si raději rozepisuju více ukazatelů do více deklarací (tj. int* p; int* q; ). Nějak stále nechápu logické důvody, proč v definici C zavedli že int* p,q nemá význam dvou ukazatelů.

… a jak vidím, epese ten zápis char str také mate, zápis char str by byl názornější.

epesi nespojuj si tu hvězdičku takto s pointerem. *str není pointer, jenom to str je pointer. Ta hvězdička je jen při deklaraci, char *str říká že str je pointer na char. Takže se proto nastaví str=“abcd”. Kdyby se použilo v programu *str, tak to znamená obsah na tom místě, kam ukazuje pointer. V tomto případě by zápis *str ukazoval na první znak řetězec, tj. písmeno “a”.

Pando, pokud Ti rozumím dobře, tak hvězdičku použiji jen při definici pointru a pak již v programu používám jen název pointru bez hvězdičky. Tomu by odpovídalo chování mého překladače při práci se znakovým pointrem - tedy str=“a”;
Jenomže pro int pointer příkaz ij=222; nic neprovede a musim napsat *ij=222;
To mi nejde stale do hlavy.

Ta hvězdička není součást názvu pointeru, to je jen takový flag. Takže při deklaraci píšeš:
char *str … to znamená proměnná s názvem str, ve které je uložena adresa na data typu char. Ta hvězdička jenom označuje, že to je ukazatel na char a ne proměnná typu char. Není součástí jména.

Když tuto proměnnou (tento ukazatel) používáš, tak pokud ji použiješ přímo, bez hvězdičky, pracuješ přímo s tou proměnnou s adresou, takže napíšeš např.:

str = “jiny text” … tedy do proměnné s názvem str uložíš adresu, kde v paměti se nachází ten textový řetězec.

Pokud v programu uvedeš hvězdičku před jménem ukazatele, znamená to obsah místa, kam ten ukazatel ukazuje. Tedy např.

int i = 5; … nějaká proměnná typu int

int *p = &i; … ukazatel na typ int, tedy proměnná obsahující adresu paměti s datovým typem int. Ta hvězdička je jen součástí deklarace, to není součást jména. V tomto případě se do ukazatele p uloží adresa proměnné i … ten znak & před jménem proměnné znamená její adresu.

p = &i; … takto se ta adresa proměnné uloží do ukazatele pokud to není už deklarace, tedy už bez hvězdičky.

Takto se později dá odvolávat na obsah cíle ukazatele, tím že se před jméno proměnné s ukazatelem uvede hvězdička. V tom případě se s tím pracuje tak, jako by se pracovalo přímo s tou cílovou proměnnou.
*p = *p + 1; … tohle vezme obsah cíle, kam ukazuje pointer, přičte 1 a uloží zas zpátky do cíle. To znamená, že tahle operace přičte 1 k obsahu proměnné i.

Ten zápis při deklaraci vlastně znamená - p je ukazatel na nějaký datový typ, *p je obsah toho místa kam ukazatel ukazuje. Deklarační zápis int *p tedy znamená, že obsah místa, kam ukazuje ukazatel, je datového typu int.

Zápis int *ij = 111 prochází překladačem jenom proto, protože i jednočipů se pracuje s adresou jako s běžným číslem. Proto tento zápis znamená ne proměnnou s hodnotou 111, ale ukazatel na paměť s adresou 111.

Pando,
tak jak to píšeš se mi to zdá celkem jednoduché a dokonce mi to připomíná i Pascal, kde implementace pointru byla čitelnějjší. Bohužel mému překladači se to nějak nezdá. Podle toho co píšeš by mělo platit jak pro číselný tak i pro znakový typ toto:
a=*ptr; kde ptr je předem definovaný a inicializovaný pointer.

Můj překladač to provede pouze pro typ char a pro typ int neudělá nic.
Dále pokud platí toto *p = *p + 1;
mělo by platit i toto *ptr=“zxc”;
bohužel, můj překladač přiřadí nesmysl. Pro typ int to ale funguje.

Tímto jsi vytvořil pointer na typ int a vložil jsi do něho hodnotu 111.
Tedy tento pointer ukazuje na adresu 111. Při tom nevíme, co je na této adrese. Takto nelze. Pointer musí ukazovat na proměnnou.

Stane se to, že pointer ij už nebude ukazovat na adresu 111, ale 222.

Pokud chceš vytvořit pointer na proměnnou “ij” , musíš nejprve tuto proměnnou deklarovat

int ij;

Pak deklaruješ pointer na tuto proměnnou a vložíš do něho její adresu

int* ij_ptr = &ij;

Pointer ij_ptr teď ukazuje na proměnnou ij. Můžeme přes něho zapisovat a číst proměnnou ij.

*ij_ptr = 50; x = *ij_ptr;

epesi zkusím Ti texty vysvětlit ještě jinak.

[code]char text]=“ahoj” … tohle je pole 4 znaků. Např. v assembleru by to vypadalo takto:

text: db ‘a’
db ‘h’
db ‘o’
db ‘j’
db 0
[/code]

[code]char* text=“ahoj” … tohle je ukazatel na text znaků (char je 1 znak), v assembleru by to bylo:

_text:db ‘a’ … pomocné pole znaků s textem
db ‘h’
db ‘o’
db ‘j’
db 0

text: dd _text … proměnná s adresou textu
[/code]
Tedy - v prvém případě nelze do proměnné text nastavit jiný text, lze jen měnit jednotlivé znaky (do velikosti délky textu). V druhém případě je to ukazatel a lze změnit adresu na který ukazuje např:

text=“123456789” … vezme text odjinud z paměti a uloží sem adresu

Díky pánové!

Co se týče číselných pointrů, tak už jsem se též prokousal z vlastních chyb a problémů s debugerem a je to přesně tak jak píše AB. Dokonce v to jde zapsat i jednodušeji (tedy v mém překladači). + Stále mám problém se znakovým pointerem.
V mémpřípadě sice platí

a=*str; - do a se přiřadí 1. znak tj. ‘1’ v případě char *str=“123”;

bohužel už nefunguje

*str=“agsjk”

A to je můj hlavní problém a nějak mě nechce nikdo vysvětlit proč to funguje pro typ int a nefunguje pro typ char.

A jdu na kutě. Třeba mě v noci osvítí nějakej ten duch a ráno budu moudřejší.
Přeji dobré noci.

To jsem se Ti právě snažil vysvětlit v tom předešlém příspěvku.

S texty se (na rozdíl od čísel) pracuje pomocí ukazatelů, zápis “123” nepředstavuje přímo text, ale ukazatel na tento text:

char *str=“123”; … uloží do pointeru str adresu textu “123”
str=“agsjk” … nastaví jiný text do ukazatele str
*str=‘x’ … tohle použije ukazatel str a do znaku na který ukazuje uloží znak ‘x’, čili ten původní text se tím změní na “xgsjk”.

Pro číslo:

int i = 111; … uloží do proměnné i číslo 111
int *p = &i; … uloží do pointeru p adresu proměnné i
*p = 222; … pomocí pointeru p uloží do proměnné i číslo 222

Pro text a číslo to nefunguje stejně, protože jedno je ukazatel a druhé je přímo hodnota.

Je to proto, že v C nelze kopírovat celý string pouhým přiřazením jako např. v Basicu:
string1 = “abcd”
string2 = string1

V C se k tomu používá funkce strcpy(char* dest, char* source) // kopíruj string

strcpy((char*)str, "agsjk");

Tak a máme tady další víkendový večer. Já, protože už mám posekanou zahradu, naštípané dříví, spravené topení a splněno spoustu dalších manželských povinností, se již můžu v klidu odávat svým počítačovým sprosťárničkám. Jestli jste ještě, pánové, svobodní, tak se ze všech sil snažte v tomto stavu setrvat c o nejdéle. I když na druhou stranu, při plnění těch povinností se zase dokonale hlava vyčistí o céčka, poiterů a jiných pitomin.
Takže jsem usedl, znovu prostudoval vaše rady, ujasnil jsem si co vím a znám, pustil debuger a pozorně tentokrát sledoval přidělování adres. A ejhle, co jsem včera neviděl a nechápal je mi dnes jasné. Jestliže jsem tedy před měsícem, když jsem začal laborovat s displejem a UARTEM žačal používat přiřazení typu

str=“1234” a nikoli *str´"1234"
dělal jsem to správně akorát jsem nevěděl proč. A to jsem se právě chtěl dozvědět od vás. Dalo vám to tedy hoši práci, jen co je pravda. Ale díky vám teď vím, že práce se znakovými a číselnými ukazateli je odlišná, předdevším kvůli odlišnému přidělovaní paměti. Též už je mi jasné proč práce hlavně se znakovými ukazateli může být dosti nebezpečná.
I když už snaj vím jak pracovat s řetězci, stejně autorům Céčka spílám dále,
neboť si myslím, že už taky mohli do svého výtvoru implementovat datový typ string, jako v jiných pořádných jazycích.

Pánové, ještě jednou dík a zase někdy nashle.

Na to bych chtěl jen upozornit, že cílem (dest) je buffer o dostatečné velikosti, nelze použít jakýkoliv ukazatel typu char*. Tato funkce jen kopíruje data, ale nevytvoří potřebný cílový buffer. Ale myslím že tohle je epesovi už dnes také jasné.

epesi ano, také se divím, proč do C nebyla zaimplementovaná kvalitnější práce s texty. Důvodem bude nejspíš to, že by vyžadovala alokaci paměti. Pro práci s texty lze využít ATL knihovny s CString, popř. jejich modifikace pro jednočipy, musí být však implementovaný manažer paměti.

Ahoj.
Precetl jsem si toto tema, ale i pres zkouseni se mi nepodarilo implementovat vase rady na svuj pripad.
Zde je muj problem:
Mam definovanou promennou

 char str[20];

Pouzivam uart komunikaci, kde je vypis znaku definovan jako extern void uart_puts(const char *s );
Z terminalu vyslu retezec “heslo:pokus”
tento retezec mam ulozeny v str a dovedu ho cely vypsat.

uart_puts(str); uart_putc('\r'); uart_putc('\n');
Ted bych chtel jenom dokazat ten text posunout tak, aby se vypsalo jenom “pokus”. Vim ze existuji funkce ktere ten znak oriznou a prepisou do jineho stringu, ale prijde mi sikovnejsi, kdyz mu jenom ukazu pozici, odkud to ma vypsat.
Dekuji

Edit:
Tak uz jsem na to prisel, stacilo definovovat

char* command=str;

a potom jenom posunout ukazatel o pocet znaku

command+=6; uart_puts(command); uart_putc('\r'); uart_putc('\n');

Nemusíš ani zavádět další proměnnou (command). Jméno pole (nebo stringu) je samo pointer na začátek pole.

Funkce uart_puts(char *s) vyžaduje jak vidět ukazatel na string.

uart_puts(str); 

Tady jsme funkci poslali ukazatel na začátek stringu “str”.
Začne vypisovat od znaku str[0].

Když chceme vypsat string až od třetího znaku, předáme funkci ukazatel na třetí znak.

uart_puts(str+2); 

Teď začne vypisovat od znaku str[2].

Aha. Diky moc, vyzkouseno funguje…