Por favor, dê uma olhada no código a seguir. Ele tenta passar uma matriz como uma char**
para uma função:
#include <stdio.h>
#include <stdlib.h>
static void printchar(char **x)
{
printf("Test: %c\n", (*x)[0]);
}
int main(int argc, char *argv[])
{
char test[256];
char *test2 = malloc(256);
test[0] = 'B';
test2[0] = 'A';
printchar(&test2); // works
printchar((char **) &test); // crashes because *x in printchar() has an invalid pointer
free(test2);
return 0;
}
O fato de que eu só posso obtê-lo para compilar lançando explicitamente &test2
para char**
já sugere que este código está errado.
Ainda assim, estou me perguntando o que exatamente está errado sobre isso. Posso passar um ponteiro para um ponteiro para uma matriz alocada dinamicamente, mas não posso passar um ponteiro para um ponteiro para uma matriz na pilha. Obviamente, posso solucionar o problema facilmente, atribuindo primeiro a matriz a uma variável temporária, da seguinte maneira:
char test[256];
char *tmp = test;
test[0] = 'B';
printchar(&tmp);
Ainda assim, alguém pode me explicar por que ele não funciona para lançar char[256]
a char**
diretamente?
char (*)[256]
parachar**
?char**
. Sem esse elenco, ele não é compilado.test
é uma matriz, não um ponteiro e&test
é um ponteiro para a matriz. Não é um ponteiro para um ponteiro.Você pode ter sido informado de que uma matriz é um ponteiro, mas isso está incorreto. O nome de uma matriz é o nome de todo o objeto - todos os elementos. Não é um ponteiro para o primeiro elemento. Na maioria das expressões, uma matriz é automaticamente convertida em um ponteiro para seu primeiro elemento. Essa é uma conveniência que geralmente é útil. Mas há três exceções a esta regra:
sizeof
.&
.Em
&test
, a matriz é o operando de&
, portanto, a conversão automática não ocorre. O resultado de&test
é um ponteiro para uma matriz de 256char
, que tem o tipochar (*)[256]
, nãochar **
.Para obter um ponteiro para um ponteiro
char
detest
, você primeiro precisa fazer um ponteiro parachar
. Por exemplo:Outra maneira de pensar sobre isso é perceber que os
test
nomes de todo o objeto - toda a matriz de 256char
. Ele não nomeia um ponteiro; portanto, em&test
, não há ponteiro cujo endereço possa ser obtido, portanto, isso não pode produzir achar **
. Para criar umchar **
, você deve primeiro ter umchar *
.fonte
_Alignof
operador mencionado além desizeof
e&
. Gostaria de saber por que eles removeram ..._Alignof
aceita apenas um nome de tipo como um operando e nunca aceitou uma matriz ou qualquer outro objeto como um operando. (Eu não sei o porquê; parece sintática e gramaticalmente que poderia sersizeof
, mas não é.)O tipo de
test2
échar *
. Portanto, o tipo de&test2
seráchar **
compatível com o tipo de parâmetrox
deprintchar()
.O tipo de
test
échar [256]
. Então, o tipo de&test
seráchar (*)[256]
qual é não compatível com o tipo de parâmetrox
deprintchar()
.Deixe-me mostrar a diferença em termos de endereços
test
etest2
.Resultado:
Aponte para observação aqui:
A saída (endereço de memória) de
test2
e&test2[0]
é numericamente igual e seu tipo também é o mesmo que échar *
.Mas os endereços
test2
e&test2
são diferentes e seu tipo também é diferente.O tipo de
test2
échar *
.O tipo de
&test2
échar **
.A saída (endereço de memória) de
test
,&test
e&test[0]
é numericamente mesmo , mas seu tipo é diferente .O tipo de
test
échar [256]
.O tipo de
&test
échar (*) [256]
.O tipo de
&test[0]
échar *
.Como mostra a saída
&test
é o mesmo que&test[0]
.Portanto, você está recebendo uma falha de segmentação.
fonte
Você não pode acessar um ponteiro para um ponteiro porque
&test
não é um ponteiro - é uma matriz.Se você pegar o endereço de uma matriz, converter a matriz e o endereço da matriz em
(void *)
e compará-los, eles (exceto o pedantry de ponteiro possível) serão equivalentes.O que você está realmente fazendo é semelhante a este (novamente, exceto com estrias alias):
o que é obviamente errado.
fonte
Seu código espera o argumento
x
deprintchar
que ponto a memória que contém uma(char *)
.Na primeira chamada, aponta para o armazenamento usado
test2
e, portanto, é realmente um valor que aponta para um(char *)
, o último apontando para a memória alocada.Na segunda chamada, no entanto, não existe um local onde esse
(char *)
valor possa ser armazenado e, portanto, é impossível apontar para essa memória. A transmissão(char **)
adicionada adicionou um erro de compilação (sobre a conversão(char *)
para(char **)
), mas não faria com que o armazenamento aparecesse do nada para conter um(char *)
inicializada para apontar para os primeiros caracteres do teste. A conversão de ponteiro em C não altera o valor real do ponteiro.Para conseguir o que deseja, você deve fazer isso explicitamente:
Suponho que seu exemplo seja uma destilação de um pedaço de código muito maior; como exemplo, talvez você queira
printchar
incrementar o(char *)
valor que ox
valor passado aponta para que, na próxima chamada, o próximo caractere seja impresso. Se não for esse o caso, por que você não passa um(char *)
apontador para o personagem a ser impresso, ou apenas passa o próprio caractere?fonte
char **
. Variáveis / objetos de matriz são simplesmente a matriz, com o endereço sendo implícito, não armazenado em nenhum lugar. Nenhum nível extra de indireção para acessá-los, ao contrário de uma variável de ponteiro que aponta para outro armazenamento.Aparentemente, pegar o endereço de
test
é o mesmo que pegar o endereço detest[0]
:Compile isso e execute:
Portanto, a causa final da falha de segmentação é que este programa tentará desreferenciar o endereço absoluto
0x42
(também conhecido como'B'
), que seu programa não tem permissão para ler.Embora com um compilador / computador diferente, os endereços sejam diferentes: Experimente online! , mas você ainda conseguirá isso, por algum motivo:
Mas o endereço que causa a falha de segmentação pode muito bem ser diferente:
fonte
test
não é o mesmo que tomar o endereço detest[0]
. O primeiro tem tipochar (*)[256]
e o último tem tipochar *
. Eles não são compatíveis, e o padrão C permite que eles tenham representações diferentes.%p
, ele deve ser convertido emvoid *
(novamente por razões de compatibilidade e representação).printchar(&test);
pode travar para você, mas o comportamento não é definido pelo padrão C e as pessoas podem observar outros comportamentos em outras circunstâncias.&test == &test[0]
viola as restrições em C 2018 6.5.9 2 porque os tipos não são compatíveis. O padrão C requer uma implementação para diagnosticar essa violação, e o comportamento resultante não é definido pelo padrão C. Isso significa que seu compilador pode produzir código avaliando-os como iguais, mas outro compilador pode não.A representação de
char [256]
depende da implementação. Não deve ser o mesmo quechar *
.Fundição
&test
de tipochar (*)[256]
dechar **
rendimentos indefinido comportamento.Em alguns compiladores, pode fazer o que você espera, e em outros não.
EDITAR:
Após testar com o gcc 9.2.1, parece que
printchar((char**)&test)
passa de fatotest
como valor convertido parachar**
. É como se a instrução fosseprintchar((char**)test)
. Naprintchar
função,x
é um ponteiro para o primeiro caractere do teste de matriz, não um ponteiro duplo para o primeiro caractere. Uma des-referência duplax
resulta em uma falha de segmentação porque os 8 primeiros bytes da matriz não correspondem a um endereço válido.Recebo exatamente o mesmo comportamento e resultado ao compilar o programa com o clang 9.0.0-2.
Isso pode ser considerado um bug do compilador ou o resultado de um comportamento indefinido cujo resultado pode ser específico do compilador.
Outro comportamento inesperado é que o código
A saída é
O comportamento estranho é isso
x
e*x
tem o mesmo valor.Isso é uma coisa do compilador. Duvido que isso seja definido pelo idioma.
fonte
char (*)[256]
depende da implementação? A representação dechar [256]
não é relevante nesta questão - é apenas um monte de bits. Mas, mesmo que você queira dizer que a representação de um ponteiro para uma matriz é diferente da representação de um ponteiro para um ponteiro, isso também perde o sentido. Mesmo se eles tiverem as mesmas representações, o código do OP não funcionaria, porque o ponteiro para um ponteiro pode ser desreferenciado duas vezes, como é feito emprintchar
, mas o ponteiro para uma matriz não pode, independentemente da representação.char (*)[256]
parachar **
é aceita pelo compilador, mas não produz o resultado esperado porque achar [256]
não é o mesmo que achar *
. Eu assumi, a codificação é diferente, caso contrário, produziria o resultado esperado.char **
, o comportamento será indefinido e que, caso contrário, se o resultado for convertido novamente emchar (*)[256]
, ele será comparado ao ponteiro original. Por "resultado esperado", você pode querer dizer que, se(char **) &test
for posteriormente convertido em achar *
, ele será comparado a&test[0]
. Isso não é um resultado improvável em implementações que usam um espaço de endereço simples, mas não é puramente uma questão de representação.char **
échar *
), o comportamento será indefinido. Caso contrário, a conversão será definida, embora o valor seja apenas parcialmente definido, conforme meu comentário acima.char (*x)[256]
não é a mesma coisa quechar **x
. O motivox
e*x
imprimir o mesmo valor do ponteiro é que elex
é simplesmente um ponteiro para a matriz. Você*x
é a matriz , e usá-la em um contexto de ponteiro decai de volta para o endereço da matriz . Nenhum bug do compilador lá (ou no que(char **)&test
faz), apenas um pouco de ginástica mental necessária para descobrir o que está acontecendo com os tipos. (cdecl explica como "declare x como ponteiro para a matriz 256 de char"). Mesmo usandochar*
para acessar a representação de objeto de umchar**
não é UB; pode alias qualquer coisa.