mais duvidas sobre ponteiros rs [RESOLVIDO]

13. Re: mais duvidas sobre ponteiros rs [RESOLVIDO]

Enzo de Brito Ferber
EnzoFerber

(usa FreeBSD)

Enviado em 02/01/2015 - 04:35h

Pra você ver como ponteiros são legais:


#include <stdio.h>

void print_bin ( int a ) {
int mask = 0x40000000; // 32bit mask - 0x8000000000 nao pode ser utilizada pois entra como numero negativo
char str[33];
register int i;

for ( i = 0; i < 32; i++ )
str[i + 1] = ( a & (mask >> i)) ? '1' : '0';

str[0] = '0';
str[ 32 ] = 0x0;

printf ( "%s\n", str );
}

int main ( void ) {
int *a, i;
// s[0] - byte menos significativo
// s[3] - byte mais significativo
char s[] = { 65, 66, 67, 0x00 };

a = (int*)&s;

printf ( "[s]: %p\n", &s );
printf ( "[a]: %p\n", &a );

printf ( "int : %d\n", (int)sizeof(int));
printf ( "char : %d\n", (int)sizeof(char));
printf ( "Valor: %d\n", *((int*)&s) );
printf ( "STR : %s\n", (char*)a );
printf ( "T(s) : %d\n", (int)sizeof(s));

for ( i = 0; i < 4; i++ )
printf ( "s[%d]: %p\n", i, &s[i] );

print_bin ( *a );

return 0;
}



O output:


[s]: 0x7fff500b9bf8
[a]: 0x7fff500b9c00
int : 4
char : 1
Valor: 4407873
STR : ABC
T(s) : 4
s[0]: 0x7fff500b9bf8
s[1]: 0x7fff500b9bf9
s[2]: 0x7fff500b9bfa
s[3]: 0x7fff500b9bfb
00000000010000110100001001000001


Recomendo o livro "Programming from the group up", disponível online gratuitamente em: (o livro está em inglês)
http://savannah.c3sl.ufpr.br//pgubook/ProgrammingGroundUp-1-0-booksize.pdf

Esse livro te mostra como os computadores funcionam em um nível primitivo, e vai te mostrando como programar usando assembly para assimilar os conceitos. Muito bom. Quando você terminar, vai entender perfeitamente como funciona ponteiros, endereços, pilhas, heap, malloc, free, brk, mmap, etc. No final das contas, é tudo endereço de memória, quem faz a conversão é o compilador, se você mandar ele converter certo, tudo fica numa boa.

printf ( "Valor: %d\n", *((int*)&s) );

Divirta-se com o livro! ;)

[]'s


  


14. Re: mais duvidas sobre ponteiros rs [RESOLVIDO]

Daniel
danielcrvg

(usa Slackware)

Enviado em 02/01/2015 - 10:47h

po sem palavras cara muito obrigado mesmo!!

eu ate sei um pouco de outras linguagens mas confesso que nenhuma me atrai o tanto quanto C..

obrigado por tudo.


15. Re: mais duvidas sobre ponteiros rs [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 03/01/2015 - 01:03h

Caro Enzo e demais colegas,

EnzoFerber escreveu:

Pra você ver como ponteiros são legais:


#include <stdio.h>

void print_bin ( int a ) {
int mask = 0x40000000; // 32bit mask - 0x8000000000 nao pode ser utilizada pois entra como numero negativo
char str[33];
register int i;

for ( i = 0; i < 32; i++ )
str[i + 1] = ( a & (mask >> i)) ? '1' : '0';

str[0] = '0';
str[ 32 ] = 0x0;

printf ( "%s\n", str );
}

int main ( void ) {
int *a, i;
// s[0] - byte menos significativo
// s[3] - byte mais significativo
char s[] = { 65, 66, 67, 0x00 };

a = (int*)&s;

printf ( "[s]: %p\n", &s );
printf ( "[a]: %p\n", &a );

printf ( "int : %d\n", (int)sizeof(int));
printf ( "char : %d\n", (int)sizeof(char));
printf ( "Valor: %d\n", *((int*)&s) );
printf ( "STR : %s\n", (char*)a );
printf ( "T(s) : %d\n", (int)sizeof(s));

for ( i = 0; i < 4; i++ )
printf ( "s[%d]: %p\n", i, &s );

print_bin ( *a );

return 0;
}



Esse programa incorre num dos erros semânticos que eu apontei anteriormente. Em todos os lugares em que você escreveu “[i]&s
” fica claro, tanto pelo contexto como pela função, que você deveria ter escrito apenas “s”. Afinal, s é um array, e um array sem subscritos representa suficientemente o endereço de seu primeiro elemento.

Outro erro que, apesar de não ter se manifestado, está latente no programa é supor que a conversão entre ponteiros e inteiros vai sempre funcionar, especialmente quando você tem ponteiros de 64 bits e inteiros de 32 bits, como no exemplo que você mesmo mostrou. Eu ouso dizer que funcionou não por sorte, mas POR AZAR. Infelizmente, a linguagem não ajuda muito, e o uso que você faz de coerção (ou conversão coercitiva, ou forçada) de tipos (type cast) impede o compilador de apontar potenciais erros, pois você diz a ele explicitamente que sabe o que está fazendo.

Se você quer converter com segurança de ponteiro para um tipo inteiro que garantidamente consegue acomodar todos os bits do ponteiro e vice-versa, deve usar o tipo intptr_t, definido em <stdint.h>. Mesmo assim, eu recomendo evitar tais conversões ao máximo, e muito cuidado nos casos em que ela for estritamente necessária.

Aquele monte de coerções de tipo de size_t para int provavelmente se deve a alguma reclamação que o compilador fez, durante a compilação, de incompatibilidade de tipos entre a conversão “%d” de printf() e o argumento a ser impresso, certo? Você pode evitar tais coerções usando o modificador “z”, transformando a especificação conversão em “%zd”, o que faz com que ela espere justamente um argumento do tipo size_t. Isso é mais seguro do que coagir os argumentos para int, o que, entre outras coisas, impede o compilador de alertar que você pode perder informação ao reduzir um valor de 64 bits para 32 bits.

Outra coisa: se você não quer ter problemas de sinal ao deslocar um valor de 32 bits para a direita, declare o valor a ser deslocado como unsigned. Do jeito como você fez, você limitou a possível faixa de valores aceitáveis pela função, obrigando o argumento a ter o bit mais significativo como zero, e imprimindo o valor errado caso tal bit seja 1.

void print_bin(register unsigned int x){
register unsigned int mask=(unsigned int)INT_MAX+1u;
while(mask){
putchar((x&mask)? '1': '0');
mask>>=1;
}
putchar('\n');
}



16. Re: mais duvidas sobre ponteiros rs [RESOLVIDO]

Enzo de Brito Ferber
EnzoFerber

(usa FreeBSD)

Enviado em 03/01/2015 - 12:51h

@paulo1205

Bom dia, meu caro.

Muito pertinentes seus comentários.
Confesso que não sabia sobre o modificador 'z' para imprimir size_t sem warnings ou casts. Boa dica.

Quanto ao programa, o único propósito foi demonstrar como pode-se fazer o que quiser com ponteiros se tiver uma ideia de como a memória esta organizada. Não recomendo usar ponteiros pra ficar brincado, mas type casts são muito úteis, basta ver a quantidade de ponteiros void como argumentos de funções (na própria libc e no kernel).

O pulo do gato é que ponteiros são apenas endereços, nada mais. Então, se você souber o conteúdo do endereço e como este está organizado, você pode manipulá-lo da forma como quiser. Foi isso o que fiz com a conversão (char []) -> (int*).

O que eu disse ao computador foi:
"No endereço apontado por s tem uma sequencia de informações que devem ser lidas como um número inteiro."
E isso foi exatamente o que o programa fez.
Os valores do array são colocados na memória (LE) assim:


ADDR char s[]
0x0010 0
0x0009 67
0x0008 66
0x0007 65 <---- s ( s[0] == 65 e s = 0x0007
0x0006 (a)0x007 <---- a == 0x0007
0x0005
...



Quando declaramos um ponteiro (int *a), informamos ao compilador C que o valor da variável 'a' é um endereço, e que quando quisermos o valor desse endereço, informaremos com um asterisco antes do nome da variável (*a), e então ele interpretará esse valor como um int. Então, quando ele encontra uma instrução do tipo (*a) ele checa o tipo da variável (int) e depois, como int's tem um tamanho de 4 bytes, ele vai ler os próximos 4 bytes de memória a partir do endereço contido em 'a' e irá tratar esses valores como um número inteiro.

Como estou em um processador Little-Endian, ele é convetido "ao contrário" byte-a-byte, como meu output mostrou. Não houve uma conversão de 8 bytes para 4 bytes. Apenas uma leitura de 4 bytes a partir de um endereço de 8 bytes apontado por um ponteiro de 8 bytes - nada de sorte (ou azar), como você disse. Os endereços x64 tem 8 bytes. As informações tem quantos bytes nós dissermos ao computador - nesse caso, 4.

Novamente, reitero: seus comentários são muito pertinentes e e todo cuidado é pouco ao manipulador ponteiros. Mas discordo que o que demostrei no programa expresse uma falha ou fraqueza da linguagem, muito pelo contrário. Pra mim, a capacidade de manipular a memória em um nível quase tão baixo quanto Assembly torna C uma linguagem muito poderosa. (Lembro sempre do Tio Ben conversando com o Peter Parker...)

Abraço!



*


P.S.: Em tempo, um programa interessante para verificar a "endianness" do computador (retirado da excelente resposta dessa pergunta http://programmers.stackexchange.com/questions/215535/regarding-little-endian-and-big-endian-convers...

#include <stdio.h>
#include <string.h>
typedef unsigned long ulong;
typedef unsigned char uchar;
int
main(int argc, char *argv[])
{
uchar word[4] = {(uchar)0x01,(uchar)0x23,(uchar)0x45,(uchar)0x67};
ulong be = 0x01234567;
ulong le = 0x67452301;
ulong me = 0x23016745;
ulong we; ulong ue;
memcpy(&we,word,4);
if( we == be ) printf("Big-endian\n");
if( we == le ) printf("Little-endian\n");
if( we == me ) printf("Middle-endian\n");

char UNIX[4+1]="UNIX";
ue = ((ulong)'U'); ue<<=8;
ue += ((ulong)'N'); ue<<=8;
ue += ((ulong)'I'); ue<<=8;
ue += ((ulong)'X');
printf("%s = %.4s\n",UNIX,(char*)&ue);

int ndx;
uchar *p = word;
printf("@%x:\n", p );
for( ndx=0; ndx<sizeof(we); ndx++ )
{
printf("[%02x] %03d:%02x\n", ndx, p[ndx], p[ndx] );
}

}





17. Re: mais duvidas sobre ponteiros rs [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 04/01/2015 - 07:18h

Caro Enzo,

Camarada, eu não que raios deu em mim, mas eu viajei na maionese quando falei da conversão entre ponteiros e inteiros. Quero dizer: o que eu disse está tecnicamente certo, só que você não usou nada disso no seu programa, pois todos os seus casts foram exclusivamente entre ponteiros.

Peço desculpas por ter falado demais e tão apaixonadamente sobre um erro que você não cometeu.

EnzoFerber escreveu:

( ... )

O pulo do gato é que ponteiros são apenas endereços, nada mais.


Do ponto de vista estritamente teórico (ou de programador Assembly), eu concordo com essa colocação.

Entretanto, no contexto da linguagem C, ponteiros carregam, durante a fase de compilação, informação sobre os tipos de dados apontados. Desse modo, eles não são "apenas endereços". São endereços, sim, mas qualificados quanto aos dados que pode haver nesses endereços. Aliás, essa é uma característica essencial para o uso do operador * ("conteúdo de").

A exceção a essa qualificação, que acaba se parecendo mais com ponteiros teóricos puros, são ponteiros do tipo void *, que são usados em C quando deliberadamente não se quer levar em consideração ou não se pode prever o tipo de dado apontado. Por sinal, o operador * não pode ser usado diretamente sobre tais ponteiros.

Então, se você souber o conteúdo do endereço e como este está organizado, você pode manipulá-lo da forma como quiser. Foi isso o que fiz com a conversão (char []) -> (int*).

O que eu disse ao computador foi:
"No endereço apontado por s tem uma sequencia de informações que devem ser lidas como um número inteiro."
E isso foi exatamente o que o programa fez.


Eis a questão: não foi bem isso o que você expressou, e o seu programa só vez o que você gostaria que ele fizesse por coincidência (daquelas que, por esconder erros, eu costumo chamar de azar, e não de sorte). Vou tentar explicar novamente, quiçá de modo mais fácil de entender.

Permita-me começar com um comentário a respeito de notação. Eu entendi perfeitamente que, ao dizer "char []", você quis dizer "array de caracteres de um tamanho qualquer" e sei que você sabe que o compilador automaticamente calculou a quantidade de elementos desse array como 4 por causa da presença da lista de inicialização de elementos na hora em que ele foi declarado. Mesmo assim, eu prefiro ser explícito num contexto como este, de explicação, porque existe um caso em que o compilador interpreta colchetes vazios (ou mesmo preenchidos) como se fosse uma declaração de ponteiro absolutamente sinônima de "char *" (a saber: na declaração de parâmetro de função -- uma reminiscência da linguagem B e das primeiríssimas versões de C, que nem mesmo possuíam a notação com asteriscos, usando sempre colchetes vazios tanto para arrays como para ponteiros). Por isso, eu prefeiro ser explícito, e dizer que o tipo de s é "char [4]".

O segundo ponto é sua coerção de tipos não foi de "char []", "char [4]" ou mesmo de "char *" para "int *", mas sim de "char (*)[4]" (ponteiro para array com quatro elementos do tipo char) para "int *", pois você usou indevidamente o operador & sobre o array s. Sua afirmação de agora confirmou a suposição que eu fiz, ao escrever que o contexto e a função deixavam claro que você queria usar apenas "s" em todos os lugares em que escreveu "&s".

Em outras palavras -- e usando suas próprias palavras -- você não disse "[no] endereço apontado por s" porque, sendo s um nome de array, o endereço por ele apontado é dado pelo seu próprio nome, sem nenhum operador. Quando você aplicou & sobre ele, você transformou sua frase em "no endereço ocupado por s" (faça a prova real: imagine a declaração "int i;" e me diga o que significa a expressão "&i").

Mas como, então, seu programa funcionou? Funcionou porque você, apesar de não ter se dado conta na hora (nem em sua última mensagem, dada a explicação que você trouxe), esbarrou coincidentemente numa idiossincrasia do C. Como o endereço inicial de um array, que é denotado por seu nome, não ocupa lugar na memória (pois é transformado numa constante durante a compilação), o operador &, ao ser aplicado sobre o nome do array, não devolve o endereço ocupado pelo nome -- pois ele não ocupa endereço algum --, mas sim o próprio valor representado por aquele nome (que é um endereço: justamente o do primeiro elemento do array), mudando, no entanto, o tipo associado a tal valor de "array com N elementos do tipo X" para "ponteiro para array com N elementos do tipo X".

( ... )

Novamente, reitero: seus comentários são muito pertinentes e e todo cuidado é pouco ao manipulador ponteiros. Mas discordo que o que demostrei no programa expresse uma falha ou fraqueza da linguagem, muito pelo contrário. Pra mim, a capacidade de manipular a memória em um nível quase tão baixo quanto Assembly torna C uma linguagem muito poderosa. (Lembro sempre do Tio Ben conversando com o Peter Parker...)


Parece-me que essa resposta (e toda aquela curiosidade sobre endianness, que eu removi agora porque não vem ao caso) se deve a eu ter dito que "[i]nfelizmente, a linguagem não ajuda".

Existem dois tipos de "a linguagem não ajuda". O primeiro tipo é o da linguagem que é tão deficiente que dificulta o trabalho (uma leitura interessante que serve de exemplo, embora descreva uma linguagem que ninguém mais usa na forma como existia então, é a do artigo "Why Pascal is not my favorite Programming Language", escrito em 1981 por Brian Kernighan, o "K" da dupla "K&R"). O outro tipo é quando a linguagem é tão permissiva que fica fácil cometer erros crassos sem que o compilador ajude a identificá-los, e algumas construções da linguagem podem até mesmo induzir ao erro ou ao seu mascaramento. Pior ainda, identificar tais erros, para poder corrigi-los, acaba se tornando uma tarefa mais difícil.

C tem exemplos dos dois tipos.

Um exemplo do primeiro caso é o suporte da linguagem a strings, que se limita em aceitar a sintaxe de constantes literais entre aspas, que a faz dispor os caracteres não-nulos em ordem na memória e os sucede com um caráter nulo. Todo o resto, incluindo as funções que podem lidar com essas sequências de N não-nulos+1 nulo está fora do núcleo da linguagem, residindo apenas na biblioteca. Isso tem óbvias vantagens para quem implementa a linguagem mas, para quem meramente a utiliza, na melhor das hipóteses não traz ganho nenhum, e na prática acaba criando limitações injustificáveis teoricamente (como, por exemplo, não poder ter um caráter nulo no meio do string).

Curiosamente, o suporte a strings acaba sendo, ao mesmo tempo, um exemplo do segundo caso. Na linguagem, além da indistinção entre arrays que representam strings e os que não os representam, residem também algumas pegadinhas, tais como a de permitir que uma constante literal entre aspas, que é, como a própria designação diz, uma constante, seja atribuída a um array ou ponteiro que não precisa ser declarado como apontando para dados constantes (que é, por sinal, o uso mais comum) e que pode, portanto, ser utilizado como argumento na chamada a uma função que pode tentar modificar o conteúdo, com consequências imprevisíveis (com um pouco de sorte, o programa capota, mesmo tendo compilado sem erros, no primeiro uso; se for azarado, aquela constante deixa de ser constante, posto que é alterada, e sai afetando coisas indesejadas ao longo da execução do programa).

Digo essas coisas porque odeio C? Não, ou eu teria de ser muito masoquista por trabalhar com ela há quase 27 anos (desde meados de 1988) e ainda participar de fóruns como este (se bem que dizem que o ódio é um companheiro mais fiel do que o amor... ;) ). Por ignorância? Também não, e espero que as colocações que faço deixem claro que eu falo com conhecimento de causa (aliás, muitos, provavelmente a vasta maioria, dos erros que eu aponto são erros que eu cansei de cometer, e sobre os quais só aprendi com o tempo).

Quando eu critico a possibilidade de, primeiro, usar o operador "endereço de" (&) sobre um ente que não possui endereço próprio, e, segundo, tal operação dar como resultado o endereço de outro objeto, só que com um tipo dado diferente do desse outro objeto, faço-o porque entendo que o primeiro ponto cria uma inconsistência ("por que para alguns entes constantes pode, e para outros não?"), e porque o segundo, além de pouquíssimo útil (nesses 27 anos, nunca vi ser usado devidamente, mas já cansei de ver ser indevidamente empregado, tanto por desatenção como por desconhcimento), cria espaço para erros fáceis de cometer e difíceis de diagnosticar.

No seu caso, não fez muita diferença porque você acabou fazendo um cast e, por isso, tanto fazia o se o valor do endereço vinha do array ou de um ponteiro para o array, dado que ambos são numericamente iguais (e eis um dos perigos também dos casts: achar que se sabe o que se está fazendo, quando na verdade não se sabe). Mas imagine se, em lugar (ou mesmo antes) do cast, você fizesse alguma aritmética com esse endereço. Uma coisa é somar n à base de um array, e outra é somar esse mesmo n a um ponteiro para um array inteiro de uma vez.

Não obstante toda essa prosa, saiba que C segue sendo uma de minhas ferramentas de trabalho favoritas. E é por isso que estamos aqui!


18. Re: mais duvidas sobre ponteiros rs [RESOLVIDO]

Enzo de Brito Ferber
EnzoFerber

(usa FreeBSD)

Enviado em 04/01/2015 - 15:42h

@paulo1205

Stack Overflow:
http://stackoverflow.com/questions/2528318/how-come-an-arrays-address-is-equal-to-its-value-in-c


The name of an array usually evaluates to the address of the first element of the array, so array and &array have the same value (but different types, so array+1 and &array+1 will not be equal if the array is more than 1 element long).
(...)

Traduzindo:

O nome de um array normalmente é interpretado como o endereço do primeiro elemento, então 'array' e '&array' tem o mesmo valor (mas diferentes tipo, então array + 1 e &array + 1 não serão iguais se o array tiver mais de um elemento.


Acho que nós dois estamos dizendo a mesma coisa com palavras diferentes.


(...)

There are two exceptions to this: when the array name is an operand of sizeof or unary & (address-of), the name refers to the array object itself. Thus sizeof array gives you the size in bytes of the entire array, not the size of a pointer.

(...)

Traduzindo:

Existem duas exceções: quando o nome do array é um operador de sizeof ou do & unário (endereço-de), o nome refere ao objeto array em si. Portanto, sizeof(array) te dá o tamanho total do array em bytes, não o tamanho de um ponteiro.



array e &array são basicamente a mesma coisa. Endereços. Eles irão mudar a forma como o compilador irá interpretar aritmética sobre eles.




char array[10];

*(array + 9); // retorna o décimo elemento de array

*(&array + 9); // retorna o décimo array com offset 9. *(&array + 9) == *(array + (10 * 9))



Novamente, não foi sorte (ou azar).

O final da resposta à pergunta no StackOverflow ainda exemplifica o que você disse na ultima resposta, que o compilador irá interpretar a operação como um tipo (char (*)[4]). Mas, o endereço inicial de ambos será o mesmo. Operações aritméticas sobre eles (offsets) serão diferentes. Seus endereços iniciais não.

Como eu não queria realizar nenhuma operação sobre eles, não fez nenhuma diferença. Por isso funcionou.


#include <stdio.h>

int main (void ) {
char array[10];

printf ( " array: %p\n", array );
printf ( "&array: %p\n\n", &array );

printf ( " (array + 9): %p\n", (array + 10));
printf ( "(&array + 9): %p\n", (&array + 10));

return 0;
}


Output:


array: 0x7fff516d4c0e
&array: 0x7fff516d4c0e

(array + 9): 0x7fff516d4c18
(&array + 9): 0x7fff516d4c72


0x72 - 0x18 = 0x5A = 90 decimal
90 = 9 * 10.

[]'s




19. Re: mais duvidas sobre ponteiros rs [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 05/01/2015 - 08:37h

EnzoFerber escreveu:

@paulo1205

Stack Overflow:
http://stackoverflow.com/questions/2528318/how-come-an-arrays-address-is-equal-to-its-value-in-c

The name of an array usually evaluates to the address of the first element of the array, so array and &array have the same value (but different types, so array+1 and &array+1 will not be equal if the array is more than 1 element long).
(...)


Traduzindo:

O nome de um array normalmente é interpretado como o endereço do primeiro elemento, então 'array' e '&array' tem o mesmo valor (mas diferentes tipo, então array + 1 e &array + 1 não serão iguais se o array tiver mais de um elemento.


Acho que nós dois estamos dizendo a mesma coisa com palavras diferentes.


Nós dois quem? Quem disse o que você colou acima foi um cara que assina como Jerry Coffin. Se esse não for seu nome de usuário no Stack Overflow, então somos ele e eu, não você e eu.

De todo modo, o Jerry e eu não concordamos plenamente. Ele diz, logo em seguida, que o tipo do símbolo array, que tenha sido declarado como

T array[N] 


é T *, e eu sustento que não é isso, mas sim T [N]. O fato de de um valor do tipo T [N] poder ser atribuído a uma variável do tipo T * não os faz idênticos, mas tão-somente compatíveis (e somente no sentido array->ponteiro; o sentido oposto não é possível já que arrays não são lvalues e não existe cast para arrays), porque ambos podem ser usados para chegar a um objeto final do tipo T.

There are two exceptions to this: when the array name is an operand of sizeof or unary & (address-of), the name refers to the array object itself. Thus sizeof array gives you the size in bytes of the entire array, not the size of a pointer.


Traduzindo:

Existem duas exceções: quando o nome do array é um operador de sizeof ou do & unário (endereço-de), o nome refere ao objeto array em si. Portanto, sizeof(array) te dá o tamanho total do array em bytes, não o tamanho de um ponteiro.


Para você ver: como o pobre Jerry inventou uma identidade onde ela não existe, teve de inventar exceções para justificar o comportamento de dois operadores nativos da linguagem ao serem aplicados sobre os arrays. Nem passou pela cabeça dele que tais exceções não precisariam existir se os tipos não fossem considerados por ele como a mesma coisa.

Eu não sei o que o Jerry acharia do programinha que vai abaixo. Talvez ele ficasse horrorizado por não ser C, mas sim C++. Ou talvez ele ficasse mais horrorizado ainda em saber que há mais do que apenas duas exceções à regra da cabeça dele.

$ cat > jerry.cc <<EOF
#include <iostream>
#include <typeinfo>

int main(){
char ac[10], *pc;
std::cout << typeid(ac).name() << ", " << typeid(pc).name() << '\n';
}
EOF
$ g++ jerry.cc -o jerry
$ ./jerry
A10_c, Pc


Tipos diferentes, c.q.d.

array e &array são basicamente a mesma coisa. Endereços. Eles irão mudar a forma como o compilador irá interpretar aritmética sobre eles.[/code]

Eu tomaria muito cuidado com esse “basicamente” -- ele contém uma verdade, mas também um bocado de dor de cabeça. E a questão toda é muito mais de sentido do que de mera aritmética.

Existe um artigo de humor (“Real Programmers don't use Pascal” -- recomendo muito a leitura, divertida e instrutiva para qualquer um na nossa área) que tem a seguinte frase “the determined Real Programmer can write FORTRAN programs in any language”. O “basicamente” que você escreveu acima vai na linha do do “Real Programmer” da comédia, mas com Assembly em lugar de Fortran.

Trazendo para o C, se você realmente só se preocupa com o endereço dos ponteiros, e não com os dados que dão sentido a tais ponteiros, talvez você se contente em usar sempre void * para todos os seus ponteiros. Seria bem “programador de verdade”. Já pensou? Um programinha simples (mesmo!!!), na casa de umas dez mil linhas, só com ponteiros sem tipo definido, fazenndo cast só na hora de mexer com os dados (se é que “programadores de verdade” fazem cast... -- fazem?). E um programa mais parrudo, então, com umas cem mil linhas? Delícia, né? E bem Assembly.

Bom, eu não acho. E, se for para usar Assembly, eu prefiro usar Assembly de verdade, não C.

[quote]Novamente, não foi sorte (ou azar).

O final da resposta à pergunta no StackOverflow ainda exemplifica o que você disse na ultima resposta, que o compilador irá interpretar a operação como um tipo (char (*)[4]). Mas, o endereço inicial de ambos será o mesmo. Operações aritméticas sobre eles (offsets) serão diferentes. Seus endereços iniciais não.

Como eu não queria realizar nenhuma operação sobre eles, não fez nenhuma diferença. Por isso funcionou.


Não há razão de ser para um programa que não os dados que ele manipula. Do mesmo modo, também são os dados -- e seus respectivos tipos -- que dão sentido a cada operação. Uma mudança de tipo é uma mudança de significado. Se ela for indevida, o que se tem é um bug, mesmo que o resultado final saia certo.

---

De todo modo, sua insistência só reforça minha tese inicial de que a possibilidade de aplicar o operador & (“endereço de”) sobre arrays é uma excrecência que deveria ser banida da linguagem. Arrays não possuem endereço próprio -- eles já são o endereço --, e a possibilidade de usar & sobre eles, por retornarem o mesmo valor numérico de endereço (ainda que com tipo trocado), produz variações de sentido que geralmente ficam escondidas pelo fato de o C ser geralmente leniente com o uso de ponteiros do tipo errado, ou mesmo não ter meios de identificar se o tipo está certo ou não.

Veja o seguinte.

char nome[50];
scanf("%49[^\n]%*[^\n]%1*[\n]", &nome);
nome[49]=0;


Esse código está semanticamente errado, pois o argumento da conversão “%[” deve ser compatível com um ponteiro para caracteres. Só que não existe erro sintático nem violação de tipos na chamada da função, pois a definição de scanf() explicitamente diz que ela aceita um string de formatação (ponteiro para caracteres constantes) seguido por qualquer quantidade de argumentos de quaisquer tipos (alguns compiladores até têm tratamentos específicos para tentar evitar erros com printf(), scanf() e congêneres, mas nenhum compilador vai conseguir pegar erros numa função semelhante que você ou eu criarmos).

E o pior é que esse programa semanticamente errado funciona, pela coincidência do valor numérico do endereço entre nome e &nome.

Agora vamos mudar o programa um pouco.

char nome[50];
char *const p=nome;
scanf("%49[^\n]%*[^\n]%1*[\n]", &p);
p[49]=0;


p é um ponteiro constante para caracteres não-constantes, e ele aponta para o mesmo lugar que nome. Esse programa tem a mesma chance de compilar que o outro, mas esse vai dar pau na execução, ao contrário daquele. Note que, em termos de sentido do que está escrito, os dois são muito parecidos -- inclusive, e fiz o ponteiro ser constante de propósito e para aproximar o sentido ao máximo. Só que agora não houve a coincidência numérica de endereços, pois p possui seu próprio endereço, e ele é dado pelo operador &, em vez do valor de p.

É claro que dá para aprender a viver com isso, aceitar como uma realidade da vida (de certa forma, eu vivo com isso e aceito, pois nunca deixei de usar C ou C++). Mas é inegável que há uma inconsistência sintática que produz erros semânticos que podem muitas vezes permanecer latentes por anos e anos.


20. Re: mais duvidas sobre ponteiros rs [RESOLVIDO]

Enzo de Brito Ferber
EnzoFerber

(usa FreeBSD)

Enviado em 05/01/2015 - 11:56h


Nós dois quem? Quem disse o que você colou acima foi um cara que assina como Jerry Coffin. Se esse não for seu nome de usuário no Stack Overflow, então somos ele e eu, não você e eu.


Paulo, calma.

Em momento algum disse que a resposta do StackOverflow era de minha autoria, nem disse que eu era o Jerry Coffin. Apenas concordo com o que ele disse. E respondendo a sua pergunta nada amigável: você e eu, mas me enganei.

Considere o seguinte programa:


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void foo ( int (*array)[5] ) {
int *p = (int*)array;
register int i;

for ( i = 0; i < 5; i++ ) printf ( "%d\n", p[ i ] );

}

void bar ( int (*array)[5] ) {
int *p = (int*)array;
register int i;

for ( i = 0; i < 5; i++ ) p[ i ] *= 2;
}

int main ( void ) {
int array[5] = { 1,2,3,4,5};

foo ( &array );

bar ( &array );

printf ( "\n\n" );
foo ( &array );
}


Output:


1
2
3
4
5


2
4
6
8
10


De todo modo, sua insistência só reforça minha tese inicial de que a possibilidade de aplicar o operador & (“endereço de”) sobre arrays é uma excrecência que deveria ser banida da linguagem. Arrays não possuem endereço próprio -- eles já são o endereço --, e a possibilidade de usar & sobre eles, por retornarem o mesmo valor numérico de endereço (ainda que com tipo trocado), produz variações de sentido que geralmente ficam escondidas pelo fato de o C ser geralmente leniente com o uso de ponteiros do tipo errado, ou mesmo não ter meios de identificar se o tipo está certo ou não.


Então, como visto no exemplo acima, não são uma anomalia da linguagem. E sim uma poderosa ferramenta. Isso nos dá a possibilidade de passar arrays de tamanho fixo como argumentos de funções e fazer com que o próprio compilador verifique se os argumentos são do tamanho certo. Porque?

Bem, como você mesmo já disse, quando se faz &array, você na verdade está dizendo ao compilador que o tipo de dados é T(*)[n]. Sendo assim, você poderá definir um tipo de dado de tamanho fixo, e o próprio type-check do compilador irá verificar se os argumentos estão corretos. Pra mim, isso é uma ferramenta poderosa da linguagem, não uma falha. E discordo de você: acho que deveria ser difundida, não banida.

Se trocarmos o tamanho do array em main para 6, obtemos:


gcc -o array2 array2.c
array2.c:23:11: warning: incompatible pointer types passing 'int (*)[6]' to
parameter of type 'int (*)[5]' [-Wincompatible-pointer-types]
foo ( &array );
^~~~~~
array2.c:5:18: note: passing argument to parameter 'array' here
void foo ( int (*array)[5] ) {
^
array2.c:25:11: warning: incompatible pointer types passing 'int (*)[6]' to
parameter of type 'int (*)[5]' [-Wincompatible-pointer-types]
bar ( &array );
^~~~~~
array2.c:13:18: note: passing argument to parameter 'array' here
void bar ( int (*array)[5] ) {
^
array2.c:28:11: warning: incompatible pointer types passing 'int (*)[6]' to
parameter of type 'int (*)[5]' [-Wincompatible-pointer-types]
foo ( &array );
^~~~~~
array2.c:5:18: note: passing argument to parameter 'array' here
void foo ( int (*array)[5] ) {
^
3 warnings generated.


Compila, mas gera warnings. Então, para nos proteger (como fazemos em compilações de grandes projetos), usamos o -Werror.

*

Quanto a arrays, sim eles não são variáveis. (Seção 5.3, Ponteiros e Array, C programming language - K & R )

MAS, existe um porém. Arrays não servem de lvalue em operações de endereçamento não porque não possuem endereços, mas porque possuem endereço constante no espaço de endereçamento do processo. Fazer &3.14159 reamente não faz sentido, mas não é o mesmo que fazer &array.


3.14159 é carregado diretamente em um registrador em tempo de execução, e esse valor está embutido diretamente nos opcodes. a += 3.14159. Ele não tem um endereço, é uma constante nos opcodes, quando a instrução for lida (addq 3.14159, %rax), ele será constante. Você não pode fazer movq (3.14159), %rax

array, por outro lado, tem sim um endereço fixo. Mas tem um endereço. O motivo pelo qual ele não serve de lvalue em operações de endereçamento é que ele é um endereço de memória, não uma referência. Seria o mesmo que fazer &ptr = ptr2. Não se pode alterar o endereço de memória das variáveis, pode-se alterar para qual endereço os ponteiros apontam. Todas as variáveis tem endereços constantes na memória (inclusive os ponteiros), mas todas têm valores variáveis. A diferença de variáveis comuns para ponteiros é que os valores dos ponteiros são outros endereços. Mas os endereços deles mesmos não é alterado hora nenhuma.


No final das contas, toda e qualquer informação no computador tem um endereço na memória (exceto constantes em opcodes, que serão carregadas diretamente nos registradores) e todo endereço de memória é exatamente idêntico a todos os outros. (o comportamento, não o endereço em si)

Fazer array, &array ou &array[0] retornará exatamente o mesmo endereço de memória. O que vai mudar é a forma como o compilador vai interpretar isso.

Acontece que quando faço &array o compilador espera que o tipo de dado que ele vai interpretar seja do tipo T(*)[], só que como faço o cast logo após, fica tudo numa boa. A meu ver, o cast não é errado. Computadores são burros, temos que mostrá-los como fazer e interpretar tudo. O cast é uma maneira de fazer o computador interpretar algo com uma forma diferente da padrão. Foi o que fiz no primeiro exemplo.

O computador esperava interpretar os dados do endereço como caracteres. E isso vai acontecer toda vez que eu tentar acessá-los usando um ponteiro para char. Mas, como mando o computador declarar um ponteiro para int e digo que esse ponteiro tem o mesmo valor do ponteiro para char, toda vez que eu acessar o valor do endereço através do ponteiro int, ele será interpretado como int. E toda vez que eu acessar o conteúdo desse mesmo endereço através de um ponteiro char, ele será interpretado como um array de caracteres. Tudo questão de interpretação. O conteúdo dos endereços de memória continua o mesmo, os endereços continuam os mesmos. O que mudou foi a forma como ele interpretou aqueles mesmos dados.


Considere uma nota escolar: A

Se você interpretar como caractere, é uma excelente nota, se interpretar como decimal, vira 65 - uma nota raspando na média. E se interpretar como hexadecimal, vira 41, bem abaixo da média. Tudo com o mesmo dado: 01000001.


Novamente, tudo é endereço. E você pode interpretá-los como bem quiser. Você só não consegue interpretar conteúdos de endereços de coisas que não têm endereços. Se tem endereço, você pode interpretar como quiser.

Mais sobre ponteiros para arrays de tamanho fixo:
http://stackoverflow.com/questions/1810083/c-pointers-pointing-to-an-array-of-fixed-size
Resposta por: AndreyT

Não fui eu quem perguntou nem respondeu.

[]'s


21. Re: mais duvidas sobre ponteiros rs [RESOLVIDO]

Enzo de Brito Ferber
EnzoFerber

(usa FreeBSD)

Enviado em 05/01/2015 - 15:03h


@paulo1205


(...)
é T *, e eu sustento que não é isso, mas sim T [N]. O fato de de um valor do tipo T [N] poder ser atribuído a uma variável do tipo T * não os faz idênticos, mas tão-somente compatíveis (e somente no sentido array->ponteiro; o sentido oposto não é possível já que arrays não são lvalues e não existe cast para arrays), porque ambos podem ser usados para chegar a um objeto final do tipo T.
(...)



(...)
Para você ver: como o pobre Jerry inventou uma identidade onde ela não existe, teve de inventar exceções para justificar o comportamento de dois operadores nativos da linguagem ao serem aplicados sobre os arrays. Nem passou pela cabeça dele que tais exceções não precisariam existir se os tipos não fossem considerados por ele como a mesma coisa.
(...)


Essa resposta aqui (http://stackoverflow.com/questions/4607128/in-c-are-arrays-pointers-or-used-as-pointers?rq=1) responde a ambas as suas afirmações:

O "pobre" Jerry não teve que inventar exceções. Elas estão no documento de padronização do C, disponível para download em:
(http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf)


6.3.2.1 Lvalues, arrays, and function designators

(...)
3 Except when it is the operand of the sizeof operator or the unary & operator, or is a
string literal used to initialize an array, an expression that has type ‘‘array of type’’ is
converted to an expression with type ‘‘pointer to type’’ that points to the initial element of
the array object and is not an lvalue. If the array object has register storage class, the
behavior is undefined.
(...)


Parte da resposta no StackOverflow (que não foi de minha autoria):


(...)
When you declare an array such as

int a[10];

the object designated by the expression a is an array (i.e., a contiguous block of memory large enough to hold 10 int values), and the type of the expression a is "10-element array of int", or int [10]. If the expression a appears in a context other than as the operand of the sizeof or & operators, then its type is implicitly converted to int *, and its value is the address of the first element.

In the case of the sizeof operator, if the operand is an expression of type T [N], then the result is the number of bytes in the array object, not in a pointer to that object: N * sizeof T.

In the case of the & operator, the value is the address of the array, which is the same as the address of the first element of the array, but the type of the expression is different: given the declaration T a[N];, the type of the expression &a is T (*)[N], or pointer to N-element array of T. The value is the same as a or &a[0] (the address of the array is the same as the address of the first element in the array), but the difference in types matters.


E a conclusão, no final, após uma ótima tabela de referência sobre essas expressões:


(...)
So, in summary: arrays are not pointers. In most contexts, array expressions are converted to pointer types.

Espero ter esclarecido meu ponto de vista.

[]'s


22. Re: mais duvidas sobre ponteiros rs [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 09/02/2015 - 21:13h

Prezados,

Quero me desculpar com todos vocês pelas besteiras que disse e pelo tom que a elas imprimi.

Ao mesmo tempo, agradeço publicamente ao Enzo por, além de ser um cavalheiro no tratamento para comigo, mesmo diante da minha indevida petulância, ter ajudado a desfazer uma compreensão errônea que eu já vinha de muitos anos a respeito de o que é um lvalue, que acabou sendo a causa do entendimento também errôneo sobre o comportamento de arrays no C.


23. Re: mais duvidas sobre ponteiros rs [RESOLVIDO]

Enzo de Brito Ferber
EnzoFerber

(usa FreeBSD)

Enviado em 10/02/2015 - 15:59h

paulo1205 escreveu:

Prezados,

Quero me desculpar com todos vocês pelas besteiras que disse e pelo tom que a elas imprimi.

Ao mesmo tempo, agradeço publicamente ao Enzo por, além de ser um cavalheiro no tratamento para comigo, mesmo diante da minha indevida petulância, ter ajudado a desfazer uma compreensão errônea que eu já vinha de muitos anos a respeito de o que é um lvalue, que acabou sendo a causa do entendimento também errôneo sobre o comportamento de arrays no C.


Boa tarde, Paulo!

Muito obrigado pela resposta e pelos elogios.
Não esquenta com o resto, tá tudo numa boa.
Discutir sempre gera conhecimento para ambos os lados.

Abraço!
Enzo Ferber



01 02



Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts