23 Código inseguro
23.1 Generalidades
Uma implementação que não suporta código não seguro é necessária para diagnosticar qualquer uso das regras sintáticas definidas nesta cláusula.
O restante desta cláusula, incluindo todas as suas subcláusulas, é condicionalmente normativo.
Nota: A linguagem C# principal, conforme definida nas cláusulas anteriores, difere notavelmente de C e C++ em sua omissão de ponteiros como um tipo de dados. Em vez disso, o C# fornece referências e a capacidade de criar objetos que são gerenciados por um coletor de lixo. Esse design, juntamente com outros recursos, torna o C# uma linguagem muito mais segura do que C ou C++. Na linguagem C# principal, simplesmente não é possível ter uma variável não inicializada, um ponteiro "pendurado" ou uma expressão que indexa uma matriz além de seus limites. Categorias inteiras de bugs que rotineiramente atormentam programas C e C++ são assim eliminadas.
Embora praticamente todas as construções de tipo de ponteiro em C ou C++ tenham uma contrapartida de tipo de referência em C#, no entanto, há situações em que o acesso a tipos de ponteiro se torna uma necessidade. Por exemplo, a interface com o sistema operacional subjacente, o acesso a um dispositivo mapeado na memória ou a implementação de um algoritmo crítico em termos de tempo podem não ser possíveis ou práticos sem acesso a ponteiros. Para atender a essa necessidade, o C# fornece a capacidade de escrever código não seguro.
Em código inseguro, é possível declarar e operar em ponteiros, realizar conversões entre ponteiros e tipos integrais, tomar o endereço de variáveis, e assim por diante. Em certo sentido, escrever código inseguro é muito parecido com escrever código C dentro de um programa C#.
Código inseguro é, na verdade, um recurso "seguro" da perspetiva de desenvolvedores e usuários. O código não seguro deve ser claramente marcado com o modificador
unsafe
, para que os desenvolvedores não possam usar recursos inseguros acidentalmente, e o mecanismo de execução trabalha para garantir que o código não seguro não possa ser executado em um ambiente não confiável.Nota final
23.2 Contextos inseguros
Os recursos não seguros do C# estão disponíveis apenas em contextos não seguros. Um contexto não seguro é introduzido pela inclusão de um unsafe
modificador na declaração de um tipo, membro ou função local, ou empregando um unsafe_statement:
- Uma declaração de uma classe, struct, interface ou delegado pode incluir um
unsafe
modificador, caso em que toda a extensão textual dessa declaração de tipo (incluindo o corpo da classe, struct ou interface) é considerada um contexto inseguro.Nota: Se o type_declaration for parcial, apenas essa parte é um contexto inseguro. Nota final
- Uma declaração de um campo, método, propriedade, evento, indexador, operador, construtor de instância, finalizador, construtor estático ou função local pode incluir um
unsafe
modificador, caso em que toda a extensão textual dessa declaração de membro é considerada um contexto inseguro. - Um unsafe_statement permite o uso de um contexto inseguro dentro de um bloco. Toda a extensão textual do bloco associado é considerada um contexto inseguro. Uma função local declarada dentro de um contexto inseguro é, em si mesma, insegura.
As extensões gramaticais associadas são mostradas abaixo e em subcláusulas subsequentes.
unsafe_modifier
: 'unsafe'
;
unsafe_statement
: 'unsafe' block
;
Exemplo: No código a seguir
public unsafe struct Node { public int Value; public Node* Left; public Node* Right; }
O
unsafe
modificador especificado na declaração struct faz com que toda a extensão textual da declaração struct se torne um contexto inseguro. Assim, é possível declarar osLeft
campos eRight
como sendo de um tipo de ponteiro. O exemplo acima também poderia ser escritopublic struct Node { public int Value; public unsafe Node* Left; public unsafe Node* Right; }
Aqui, os
unsafe
modificadores nas declarações de campo fazem com que essas declarações sejam consideradas contextos inseguros.Exemplo final
Além de estabelecer um contexto inseguro, permitindo assim o uso de tipos de ponteiro, o unsafe
modificador não tem efeito sobre um tipo ou um membro.
Exemplo: No código a seguir
public class A { public unsafe virtual void F() { char* p; ... } } public class B : A { public override void F() { base.F(); ... } }
O modificador inseguro no
F
método emA
simplesmente faz com que a extensão textual seF
torne um contexto inseguro no qual os recursos inseguros da linguagem podem ser usados. Na substituição de inF
, não há necessidade deB
reespecificar ounsafe
modificador — a menos, é claro, que oF
método emB
si precise de acesso a recursos inseguros.A situação é ligeiramente diferente quando um tipo de ponteiro faz parte da assinatura do método
public unsafe class A { public virtual void F(char* p) {...} } public class B: A { public unsafe override void F(char* p) {...} }
Aqui, como
F
a assinatura do inclui um tipo de ponteiro, ela só pode ser escrita em um contexto inseguro. No entanto, o contexto inseguro pode ser introduzido tornando toda a classe insegura, como é o caso emA
, ou incluindo umunsafe
modificador na declaração de método, como é o caso emB
.Exemplo final
Quando o unsafe
modificador é usado em uma declaração de tipo parcial (§15.2.7), apenas essa parte específica é considerada um contexto inseguro.
23.3 Tipos de ponteiro
Num contexto inseguro, um tipo (§8.1) pode ser um pointer_type, bem como um value_type, um reference_type ou um type_parameter. Num contexto não seguro, um pointer_type também pode ser o tipo de elemento de uma matriz (§17). Um pointer_type também pode ser usado em um tipo de expressão (§12.8.18) fora de um contexto inseguro (como tal uso não é inseguro).
Um pointer_type é escrito como um unmanaged_type (§8.8) ou a palavra-chave void
, seguido por um *
token:
pointer_type
: value_type ('*')+
| 'void' ('*')+
;
O tipo especificado antes do *
em um tipo de ponteiro é chamado de tipo referente do tipo de ponteiro. Ele representa o tipo da variável para a qual um valor do tipo de ponteiro aponta.
Um pointer_type só pode ser utilizado num array_type num contexto não seguro (§23.2). Um non_array_type é qualquer tipo que não seja em si um array_type.
Ao contrário das referências (valores dos tipos de referência), os ponteiros não são rastreados pelo coletor de lixo — o coletor de lixo não tem conhecimento dos ponteiros e dos dados para os quais eles apontam. Por esta razão, um ponteiro não tem permissão para apontar para uma referência ou para uma struct que contenha referências, e o tipo referente de um ponteiro deve ser um unmanaged_type. Os próprios tipos de ponteiro são tipos não gerenciados, portanto, um tipo de ponteiro pode ser usado como o tipo referente para outro tipo de ponteiro.
A regra intuitiva para a mistura de ponteiros e referências é que os referentes de referências (objetos) podem conter ponteiros, mas os referentes de ponteiros não podem conter referências.
Exemplo: Alguns exemplos de tipos de ponteiro são dados na tabela abaixo:
Exemplo Descrição byte*
Ponteiro para byte
char*
Ponteiro para char
int**
Ponteiro para ponteiro para int
int*[]
Matriz unidimensional de ponteiros para int
void*
Ponteiro para tipo desconhecido Exemplo final
Para uma determinada implementação, todos os tipos de ponteiro devem ter o mesmo tamanho e representação.
Nota: Ao contrário de C e C++, quando vários ponteiros são declarados na mesma declaração, em C# o
*
é escrito apenas com o tipo subjacente, não como um pontuador de prefixo em cada nome de ponteiro. Por exemplo:int* pi, pj; // NOT as int *pi, *pj;
Nota final
O valor de um ponteiro com tipo T*
representa o endereço de uma variável do tipo T
. O operador *
de indireção do ponteiro (§23.6.2) pode ser usado para acessar essa variável.
Exemplo: Dada uma variável
P
do tipoint*
, a expressão*P
denota aint
variável encontrada no endereço contido emP
. Exemplo final
Como uma referência de objeto, um ponteiro pode ser null
. A aplicação do operador indirection a um null
ponteiro valorizado resulta em comportamento definido pela implementação (§23.6.2). Um ponteiro com valor null
é representado por all-bits-zero.
O void*
tipo representa um ponteiro para um tipo desconhecido. Como o tipo referente é desconhecido, o operador indirection não pode ser aplicado a um ponteiro do tipo void*
, nem qualquer aritmética pode ser executada em tal ponteiro. No entanto, um ponteiro do tipo void*
pode ser convertido para qualquer outro tipo de ponteiro (e vice-versa) e comparado com valores de outros tipos de ponteiro (§23.6.8).
Os tipos de ponteiro são uma categoria separada de tipos. Ao contrário dos tipos de referência e dos tipos de valor, os tipos de ponteiro não herdam e não existem conversões entre os tipos de object
ponteiro e object
o . Em particular, boxe e unboxing (§8.3.13) não são suportados para ponteiros. No entanto, são permitidas conversões entre diferentes tipos de ponteiro e entre tipos de ponteiro e os tipos integrais. Isto é descrito no §23.5.
Um pointer_type não pode ser usado como um argumento de tipo (§8.4), e a inferência de tipo (§12.6.3) falha em chamadas de método genérico que teriam inferido um argumento de tipo como sendo um tipo de ponteiro.
Um pointer_type não pode ser utilizado como um tipo de subexpressão de uma operação dinamicamente ligada (§12.3.3).
Um pointer_type não pode ser utilizado como o tipo do primeiro parâmetro num método de extensão (§15.6.10).
Um pointer_type pode ser usado como o tipo de campo volátil (§15.5.4).
A eliminação dinâmica de um tipo E*
é o tipo de ponteiro com o tipo referente da eliminação dinâmica de E
.
Uma expressão com um tipo de ponteiro não pode ser usada para fornecer o valor em um member_declarator dentro de um anonymous_object_creation_expression (§12.8.17.7).
O valor padrão (§9.3) para qualquer tipo de ponteiro é null
.
Nota: Embora os ponteiros possam ser passados como parâmetros de referência, isso pode causar um comportamento indefinido, uma vez que o ponteiro pode muito bem ser definido para apontar para uma variável local que não existe mais quando o método chamado retorna, ou o objeto fixo para o qual ele costumava apontar, não é mais fixo. Por exemplo:
class Test { static int value = 20; unsafe static void F(out int* pi1, ref int* pi2) { int i = 10; pi1 = &i; // return address of local variable fixed (int* pj = &value) { // ... pi2 = pj; // return address that will soon not be fixed } } static void Main() { int i = 15; unsafe { int* px1; int* px2 = &i; F(out px1, ref px2); int v1 = *px1; // undefined int v2 = *px2; // undefined } } }
Nota final
Um método pode retornar um valor de algum tipo, e esse tipo pode ser um ponteiro.
Exemplo: Quando dado um ponteiro para uma sequência contígua de s, a contagem de
int
elementos dessa sequência e algum outroint
valor, o método a seguir retorna o endereço desse valor nessa sequência, se ocorrer uma correspondência, caso contrário, ele retornaránull
:unsafe static int* Find(int* pi, int size, int value) { for (int i = 0; i < size; ++i) { if (*pi == value) { return pi; } ++pi; } return null; }
Exemplo final
Em um contexto inseguro, várias construções estão disponíveis para operar em ponteiros:
- O operador unário
*
pode ser usado para executar a indireção do ponteiro (§23.6.2). - O
->
operador pode ser utilizado para aceder a um membro de uma estrutura através de um ponteiro (§23.6.3). - O
[]
operador pode ser utilizado para indexar um ponteiro (§23.6.4). - O operador unário
&
pode ser utilizado para obter o endereço de uma variável (§23.6.5). - Os
++
operadores e--
podem ser utilizados para aumentar e diminuir os ponteiros (§23.6.6). - O binário e
+
os operadores podem ser usados para executar a aritmética de-
ponteiros (§23.6.7). - Os
==
operadores ,!=
,<
,>
,<=
, e>=
podem ser usados para comparar ponteiros (§23.6.8). - O
stackalloc
operador pode ser usado para alocar memória da pilha de chamadas (§23.9). - A
fixed
declaração pode ser usada para fixar temporariamente uma variável para que seu endereço possa ser obtido (§23.7).
23.4 Variáveis fixas e móveis
O endereço do operador (§23.6.5) e a declaração (fixed
) dividem as variáveis em duas categorias: variáveis fixas e variáveis móveis.
As variáveis fixas residem em locais de armazenamento que não são afetados pela operação do coletor de lixo. (Exemplos de variáveis fixas incluem variáveis locais, parâmetros de valor e variáveis criadas por ponteiros de desreferenciação.) Por outro lado, as variáveis móveis residem em locais de armazenamento sujeitos a realocação ou descarte pelo coletor de lixo. (Exemplos de variáveis móveis incluem campos em objetos e elementos de matrizes.)
O &
operador (§23.6.5) permite obter o endereço de uma variável fixa sem restrições. No entanto, uma vez que uma variável móvel está sujeita a deslocalização ou eliminação pelo coletor de lixo, o endereço de uma variável móvel só pode ser obtido utilizando um fixed statement
(§23.7), e esse endereço permanece válido apenas durante a vigência dessa fixed
declaração.
Em termos precisos, uma variável fixa é uma das seguintes:
- Uma variável resultante de uma simple_name (§12.8.4) que se refere a uma variável local, parâmetro de valor ou matriz de parâmetros, a menos que a variável seja capturada por uma função anónima (§12.19.6.2).
- Uma variável resultante de um member_access (§12.8.7) do formulário
V.I
, ondeV
é uma variável fixa de um struct_type. - Uma variável resultante de um pointer_indirection_expression (§23.6.2) do formulário , de um pointer_member_access (
*P
) do formulário ou de um pointer_element_access (P->I
) do formulário .P[E]
Todas as outras variáveis são classificadas como variáveis móveis.
Um campo estático é classificado como uma variável móvel. Além disso, um parâmetro por referência é classificado como uma variável móvel, mesmo que o argumento dado para o parâmetro seja uma variável fixa. Finalmente, uma variável produzida pela desreferenciação de um ponteiro é sempre classificada como uma variável fixa.
23.5 Conversões de ponteiro
23.5.1 Generalidades
Em um contexto inseguro, o conjunto de conversões implícitas disponíveis (§10.2) é estendido para incluir as seguintes conversões implícitas de ponteiro:
- De qualquer pointer_type para o tipo
void*
. - Do
null
literal (§6.4.5.7) a qualquer pointer_type.
Além disso, em um contexto inseguro, o conjunto de conversões explícitas disponíveis (§10.3) é estendido para incluir as seguintes conversões explícitas de ponteiro:
- De qualquer pointer_type para qualquer outra pointer_type.
- De
sbyte
,byte
,short
, ,ushort
int
,uint
,long
, ouulong
para qualquer pointer_type. - De qualquer pointer_type a
sbyte
,byte
,short
,ushort
,int
,uint
,long
, ouulong
.
Finalmente, em um contexto inseguro, o conjunto de conversões implícitas padrão (§10.4.2) inclui as seguintes conversões de ponteiro:
- De qualquer pointer_type para o tipo
void*
. - Do
null
literal a qualquer pointer_type.
As conversões entre dois tipos de ponteiro nunca alteram o valor real do ponteiro. Em outras palavras, uma conversão de um tipo de ponteiro para outro não tem efeito sobre o endereço subjacente fornecido pelo ponteiro.
Quando um tipo de ponteiro é convertido em outro, se o ponteiro resultante não estiver alinhado corretamente para o tipo apontado, o comportamento será indefinido se o resultado for desreferenciado. Em geral, o conceito "corretamente alinhado" é transitivo: se um ponteiro para digitar A
está alinhado corretamente para um ponteiro digitar B
, que, por sua vez, está alinhado corretamente para um ponteiro digitar C
, então um ponteiro para digitar A
é alinhado corretamente para um ponteiro digitar C
.
Exemplo: Considere o seguinte caso em que uma variável com um tipo é acessada através de um ponteiro para um tipo diferente:
unsafe static void M() { char c = 'A'; char* pc = &c; void* pv = pc; int* pi = (int*)pv; // pretend a 16-bit char is a 32-bit int int i = *pi; // read 32-bit int; undefined *pi = 123456; // write 32-bit int; undefined }
Exemplo final
Quando um tipo de ponteiro é convertido em um ponteiro para byte
, o resultado aponta para o endereço byte
mais baixo da variável. Incrementos sucessivos do resultado, até o tamanho da variável, produzem ponteiros para os bytes restantes dessa variável.
Exemplo: O método a seguir exibe cada um dos oito bytes em um
double
como um valor hexadecimal:class Test { static void Main() { double d = 123.456e23; unsafe { byte* pb = (byte*)&d; for (int i = 0; i < sizeof(double); ++i) { Console.Write($" {*pb++:X2}"); } Console.WriteLine(); } } }
É claro que a produção depende da endianidade. Uma possibilidade é
" BA FF 51 A2 90 6C 24 45"
.Exemplo final
Os mapeamentos entre ponteiros e inteiros são definidos pela implementação.
Nota: No entanto, em arquiteturas de CPU de 32 e 64 bits com um espaço de endereçamento linear, as conversões de ponteiros de ou para tipos integrais normalmente se comportam exatamente como conversões de
uint
ouulong
valores, respectivamente, para ou desses tipos integrais. Nota final
23.5.2 Matrizes de ponteiro
Matrizes de ponteiros podem ser construídas usando array_creation_expression (§12.8.17.5) em um contexto inseguro. Apenas algumas das conversões que se aplicam a outros tipos de matriz são permitidas em matrizes de ponteiro:
- A conversão de referência implícita (§10.2.8) de qualquer array_type para
System.Array
e as interfaces que implementa também se aplica a matrizes de ponteiro. No entanto, qualquer tentativa de acessar os elementos da matriz atravésSystem.Array
ou as interfaces que implementa pode resultar em uma exceção em tempo de execução, já que os tipos de ponteiro não são conversíveis emobject
. - As conversões de referência implícitas e explícitas (§10.2.8, §10.3.5) de um tipo
S[]
de matriz unidimensional paraSystem.Collections.Generic.IList<T>
e suas interfaces de base genéricas nunca se aplicam a matrizes de ponteiro. - A conversão de referência explícita (§10.3.5) de e as interfaces que implementa para qualquer
System.Array
aplica-se a matrizes de ponteiro. - As conversões de referência explícitas (§10.3.5) de e suas interfaces base para um tipo
System.Collections.Generic.IList<S>
de matriz unidimensional nunca se aplicam a matrizes de ponteiro, uma vez que os tipos deT[]
ponteiro não podem ser usados como argumentos de tipo e não há conversões de tipos de ponteiro para tipos sem ponteiro.
Essas restrições significam que a expansão da foreach
instrução sobre matrizes descrita no §9.4.4.17 não pode ser aplicada a matrizes de ponteiro. Em vez disso, uma foreach
declaração do formulário
foreach (V v in x)
embedded_statement
onde o tipo de é um tipo de matriz do formulário x
, T[,,...,]
é o número de dimensões menos 1 e ou T
é um tipo de V
ponteiro, é expandido usando loops for-loops aninhados da seguinte maneira:
{
T[,,...,] a = x;
for (int i0 = a.GetLowerBound(0); i0 <= a.GetUpperBound(0); i0++)
{
for (int i1 = a.GetLowerBound(1); i1 <= a.GetUpperBound(1); i1++)
{
...
for (int in = a.GetLowerBound(n); in <= a.GetUpperBound(n); in++)
{
V v = (V)a[i0,i1,...,in];
*embedded_statement*
}
}
}
}
As variáveis a
, i0
, i1
, ...
in
não são visíveis ou acessíveis ao x
embedded_statement ou a qualquer outro código-fonte do programa. A variável v
é somente leitura na instrução incorporada. Se não houver uma conversão explícita (§23.5) de (o tipo de T
elemento) para V
, um erro é produzido e nenhuma outra etapa é tomada. Se x
tiver o valor null
, a System.NullReferenceException
é lançado em tempo de execução.
Nota: Embora os tipos de ponteiro não sejam permitidos como argumentos de tipo, matrizes de ponteiro podem ser usadas como argumentos de tipo. Nota final
23.6 Ponteiros em expressões
23.6.1 Generalidades
Em um contexto inseguro, uma expressão pode produzir um resultado de um tipo de ponteiro, mas fora de um contexto inseguro, é um erro em tempo de compilação para uma expressão ser de um tipo de ponteiro. Em termos precisos, fora de um contexto inseguro, ocorre um erro em tempo de compilação se qualquer simple_name (§12.8.4), member_access (§12.8.7), invocation_expression (§12.8.10) ou element_access (§12.8.12) for do tipo ponteiro.
Num contexto inseguro, as produções primary_no_array_creation_expression (§12.8) e unary_expression (§12.9) permitem construções adicionais, que são descritas nas subcláusulas seguintes.
Nota: A precedência e associatividade dos operadores inseguros está implícita na gramática. Nota final
23.6.2 Indirection do ponteiro
Um pointer_indirection_expression consiste num asterisco (*
) seguido de um unary_expression.
pointer_indirection_expression
: '*' unary_expression
;
O operador unário *
denota indirection do ponteiro e é usado para obter a variável para a qual um ponteiro aponta. O resultado da avaliação *P
, onde P
é uma expressão de um tipo T*
de ponteiro, é uma variável do tipo T
. É um erro em tempo de compilação aplicar o operador unário *
a uma expressão do tipo void*
ou a uma expressão que não é de um tipo de ponteiro.
O efeito da aplicação do operador unário *
a um null
ponteiro valorizado é definido pela implementação. Em particular, não há garantia de que esta operação lance um System.NullReferenceException
.
Se um valor inválido tiver sido atribuído ao ponteiro, o comportamento do operador unário *
será indefinido.
Nota: Entre os valores inválidos para desreferenciar um ponteiro pelo operador unário
*
estão um endereço alinhado inadequadamente para o tipo apontado (ver exemplo no §23.5) e o endereço de uma variável após o fim de sua vida útil.
Para fins de análise de atribuição definida, uma variável produzida pela avaliação de uma expressão da forma *P
é considerada inicialmente atribuída (§9.4.2).
23.6.3 Acesso de membro do ponteiro
Um pointer_member_access consiste em um primary_expression, seguido por um token "->
", seguido por um identificador e um type_argument_list opcional.
pointer_member_access
: primary_expression '->' identifier type_argument_list?
;
Num membro do ponteiro, o acesso ao formulário P->I
deve P
ser uma expressão de um tipo de ponteiro e I
deve indicar um membro acessível do tipo ao qual P
os pontos.
Um acesso de membro de ponteiro do formulário P->I
é avaliado exatamente como (*P).I
. Para uma descrição do operador de indirection do ponteiro (*
), ver §23.6.2. Para uma descrição do operador de acesso de membro (.
), ver §12.8.7.
Exemplo: No código a seguir
struct Point { public int x; public int y; public override string ToString() => $"({x},{y})"; } class Test { static void Main() { Point point; unsafe { Point* p = &point; p->x = 10; p->y = 20; Console.WriteLine(p->ToString()); } } }
O
->
operador é usado para acessar campos e invocar um método de uma struct através de um ponteiro. Como a operaçãoP->I
é precisamente equivalente a(*P).I
, oMain
método poderia igualmente ter sido escrito:class Test { static void Main() { Point point; unsafe { Point* p = &point; (*p).x = 10; (*p).y = 20; Console.WriteLine((*p).ToString()); } } }
Exemplo final
23.6.4 Acesso ao elemento de ponteiro
Um pointer_element_access consiste em uma primary_no_array_creation_expression seguida por uma expressão entre "[
" e "]
".
pointer_element_access
: primary_no_array_creation_expression '[' expression ']'
;
Num elemento de ponteiro, o acesso ao formulário P[E]
, P
deve ser uma expressão de um tipo de ponteiro diferente de void*
, e E
deve ser uma expressão que pode ser implicitamente convertida em int
, uint
, long
, ou ulong
.
O acesso de um elemento de ponteiro do formulário P[E]
é avaliado exatamente como *(P + E)
. Para uma descrição do operador de indirection do ponteiro (*
), ver §23.6.2. Para uma descrição do operador de adição de ponteiro (+
), ver §23.6.7.
Exemplo: No código a seguir
class Test { static void Main() { unsafe { char* p = stackalloc char[256]; for (int i = 0; i < 256; i++) { p[i] = (char)i; } } } }
Um acesso ao elemento de ponteiro é usado para inicializar o buffer de caracteres em um
for
loop. Como a operaçãoP[E]
é precisamente equivalente a*(P + E)
, o exemplo poderia igualmente ter sido escrito:class Test { static void Main() { unsafe { char* p = stackalloc char[256]; for (int i = 0; i < 256; i++) { *(p + i) = (char)i; } } } }
Exemplo final
O operador de acesso ao elemento de ponteiro não verifica se há erros fora dos limites e o comportamento ao acessar um elemento fora dos limites é indefinido.
Nota: Isto é o mesmo que C e C++. Nota final
23.6.5 Endereço do operador
Um addressof_expression consiste em um E comercial (&
) seguido de um unary_expression.
addressof_expression
: '&' unary_expression
;
Dada uma expressão E
que é de um tipo T
e é classificada como uma variável fixa (§23.4), o constructo &E
calcula o endereço da variável dado por E
. O tipo do resultado é T*
e é classificado como um valor. Um erro em tempo de compilação ocorre se E
não for classificado como uma variável, se E
for classificado como uma variável local somente leitura ou se E
denotar uma variável móvel. No último caso, uma declaração fixa (§23.7) pode ser usada para "corrigir" temporariamente a variável antes de obter seu endereço.
Nota: Como declarado no §12.8.7, fora de um construtor de instância ou construtor estático para uma struct ou classe que define um
readonly
campo, esse campo é considerado um valor, não uma variável. Como tal, o seu endereço não pode ser tomado. Da mesma forma, o endereço de uma constante não pode ser tomado. Nota final
O &
operador não requer que seu argumento seja definitivamente atribuído, mas após uma &
operação, a variável à qual o operador é aplicado é considerada definitivamente atribuída no caminho de execução no qual a operação ocorre. É responsabilidade do programador garantir que a inicialização correta da variável realmente ocorra nessa situação.
Exemplo: No código a seguir
class Test { static void Main() { int i; unsafe { int* p = &i; *p = 123; } Console.WriteLine(i); } }
i
é considerado definitivamente atribuído após a&i
operação usada para inicializarp
o . A atribuição para*p
em vigor inicializai
, mas a inclusão dessa inicialização é de responsabilidade do programador, e nenhum erro em tempo de compilação ocorreria se a atribuição fosse removida.Exemplo final
Nota: As regras de atribuição definida para o operador existem de tal forma que a
&
inicialização redundante de variáveis locais pode ser evitada. Por exemplo, muitas APIs externas levam um ponteiro para uma estrutura que é preenchida pela API. As chamadas para essas APIs normalmente passam o endereço de uma variável struct local e, sem a regra, a inicialização redundante da variável struct seria necessária. Nota final
Nota: Quando uma variável local, um parâmetro de valor ou uma matriz de parâmetros é capturada por uma função anónima (§12.8.24), essa variável, parâmetro ou matriz de parâmetros local deixa de ser considerada uma variável fixa (§23.7), passando a ser considerada uma variável móvel. Assim, é um erro para qualquer código não seguro tomar o endereço de uma variável local, parâmetro de valor ou matriz de parâmetros que foi capturado por uma função anônima. Nota final
23.6.6 Incremento e decréscimo do ponteiro
Em um contexto inseguro, os ++
operadores e --
(§12.8.16 e §12.9.6) podem ser aplicados a variáveis de ponteiro de todos os tipos, exceto void*
. Assim, para cada tipo T*
de ponteiro, os seguintes operadores são implicitamente definidos:
T* operator ++(T* x);
T* operator --(T* x);
Os operadores produzem os mesmos resultados que x+1
e x-1
, respetivamente (§23.6.7). Em outras palavras, para uma variável de ponteiro do tipo T*
, o ++
operador adiciona sizeof(T)
ao endereço contido na variável e o --
operador subtrai sizeof(T)
do endereço contido na variável.
Se uma operação de incremento ou decréscimo de ponteiro estourar o domínio do tipo de ponteiro, o resultado será definido pela implementação, mas nenhuma exceção será produzida.
23.6.7 Aritmética dos ponteiros
Em um contexto inseguro, o operador (+
) e o -
operador (§12.10.6) podem ser aplicados a valores de todos os tipos de ponteiro, exceto void*
. Assim, para cada tipo T*
de ponteiro, os seguintes operadores são implicitamente definidos:
T* operator +(T* x, int y);
T* operator +(T* x, uint y);
T* operator +(T* x, long y);
T* operator +(T* x, ulong y);
T* operator +(int x, T* y);
T* operator +(uint x, T* y);
T* operator +(long x, T* y);
T* operator +(ulong x, T* y);
T* operator –(T* x, int y);
T* operator –(T* x, uint y);
T* operator –(T* x, long y);
T* operator –(T* x, ulong y);
long operator –(T* x, T* y);
Dada uma expressão P
de um tipo T*
de ponteiro e uma expressão N
do tipo int
, uint
, long
, ou ulong
, as expressões P + N
e N + P
calcular o valor de ponteiro do tipo T*
que resulta da adição N * sizeof(T)
ao endereço fornecido por P
. Da mesma forma, a expressão P – N
calcula o valor de ponteiro do tipo T*
que resulta da subtração N * sizeof(T)
do endereço fornecido por P
.
Dadas duas expressões, e P
, de um tipo Q
de ponteiro , a expressão T*
calcula a diferença entre os endereços dados por P – Q
e P
e, em seguida, Q
divide essa diferença por sizeof(T)
. O tipo de resultado é sempre long
. Com efeito, P - Q
é calculado como ((long)(P) - (long)(Q)) / sizeof(T)
.
Exemplo:
class Test { static void Main() { unsafe { int* values = stackalloc int[20]; int* p = &values[1]; int* q = &values[15]; Console.WriteLine($"p - q = {p - q}"); Console.WriteLine($"q - p = {q - p}"); } } }
que produz a produção:
p - q = -14 q - p = 14
Exemplo final
Se uma operação aritmética de ponteiro estourar o domínio do tipo de ponteiro, o resultado será truncado de forma definida pela implementação, mas nenhuma exceção será produzida.
23.6.8 Comparação de ponteiros
Em um contexto inseguro, os ==
operadores , , !=
, <
>
, <=
, e >=
(§12.12) podem ser aplicados a valores de todos os tipos de ponteiro. Os operadores de comparação de ponteiro são:
bool operator ==(void* x, void* y);
bool operator !=(void* x, void* y);
bool operator <(void* x, void* y);
bool operator >(void* x, void* y);
bool operator <=(void* x, void* y);
bool operator >=(void* x, void* y);
Como existe uma conversão implícita de qualquer tipo de ponteiro para o void*
tipo, operandos de qualquer tipo de ponteiro podem ser comparados usando esses operadores. Os operadores de comparação comparam os endereços fornecidos pelos dois operandos como se fossem inteiros não assinados.
23.6.9 A dimensão do operador
Para certos tipos predefinidos (§12.8.19), o sizeof
operador produz um valor constante int
. Para todos os outros tipos, o sizeof
resultado do operador é definido pela implementação e é classificado como um valor, não uma constante.
A ordem na qual os membros são empacotados em uma estrutura não é especificada.
Para fins de alinhamento, pode haver preenchimento sem nome no início de uma struct, dentro de uma struct e no final da struct. O conteúdo dos bits usados como preenchimento é indeterminado.
Quando aplicado a um operando que tenha o tipo struct, o resultado é o número total de bytes em uma variável desse tipo, incluindo qualquer preenchimento.
23.7 A declaração fixa
Em um contexto inseguro, a produção de embedded_statement (§13.1) permite uma construção adicional, a instrução fixa, que é usada para "fixar" uma variável móvel de tal forma que seu endereço permaneça constante durante a duração da declaração.
fixed_statement
: 'fixed' '(' pointer_type fixed_pointer_declarators ')' embedded_statement
;
fixed_pointer_declarators
: fixed_pointer_declarator (',' fixed_pointer_declarator)*
;
fixed_pointer_declarator
: identifier '=' fixed_pointer_initializer
;
fixed_pointer_initializer
: '&' variable_reference
| expression
;
Cada fixed_pointer_declarator declara uma variável local do pointer_type dado e inicializa essa variável local com o endereço calculado pelo fixed_pointer_initializer correspondente. Uma variável local declarada em uma instrução fixa é acessível em qualquer fixed_pointer_initializerque ocorra à direita da declaração dessa variável e na embedded_statement da instrução fixa. Uma variável local declarada por uma instrução fixa é considerada somente leitura. Um erro em tempo de compilação ocorre se a instrução incorporada tentar modificar essa variável local (via atribuição ou os ++
operadores and --
) ou passá-la como um parâmetro de referência ou saída.
É um erro usar uma variável local capturada (§12.19.6.2), parâmetro de valor ou matriz de parâmetros em um fixed_pointer_initializer. Um fixed_pointer_initializer pode ser um dos seguintes:
- O token "
&
" seguido de um variable_reference (§9.5) para uma variável móvel (§23.4) de um tipoT
não gerenciado , desde que o tipoT*
seja implicitamente conversível para o tipo de ponteiro dado nafixed
instrução. Nesse caso, o inicializador calcula o endereço da variável dada, e a variável tem a garantia de permanecer em um endereço fixo durante a duração da instrução fixa. - Uma expressão de um array_type com elementos de um tipo
T
não gerenciado, desde que o tipoT*
seja implicitamente conversível para o tipo de ponteiro fornecido na instrução fixa. Nesse caso, o inicializador calcula o endereço do primeiro elemento na matriz, e toda a matriz tem a garantia de permanecer em um endereço fixo durante a duração dafixed
instrução. Se a expressão da matriz fornull
ou se a matriz tiver elementos zero, o inicializador calculará um endereço igual a zero. - Uma expressão do tipo
string
, desde que o tipochar*
seja implicitamente conversível para o tipo de ponteiro fornecido nafixed
instrução. Nesse caso, o inicializador calcula o endereço do primeiro caractere na cadeia de caracteres, e toda a cadeia de caracteres tem a garantia de permanecer em um endereço fixo durante a duração dafixed
instrução. O comportamento da instrução é definido pela implementação se a expressão de cadeia defixed
caracteres fornull
. - Uma expressão de tipo diferente de array_type ou , desde que exista um método acessível ou um método de extensão acessível que corresponda à assinatura
string
, onderef [readonly] T GetPinnableReference()
é umT
e é implicitamente convertível para o tipo de ponteiro fornecido naT*
instruçãofixed
. Nesse caso, o inicializador calcula o endereço da variável retornada, e essa variável tem a garantia de permanecer em um endereço fixo durante afixed
duração da instrução. UmGetPinnableReference()
método pode ser usado pela instrução quando afixed
resolução de sobrecarga (§12.6.4) produz exatamente um membro da função e esse membro da função satisfaz as condições anteriores. OGetPinnableReference
método deve retornar uma referência a um endereço igual a zero, como aquele retornado deSystem.Runtime.CompilerServices.Unsafe.NullRef<T>()
quando não há dados para fixar. - Um simple_name ou member_access que faz referência a um membro de buffer de tamanho fixo de uma variável móvel, desde que o tipo do membro de buffer de tamanho fixo seja implicitamente conversível para o tipo de ponteiro fornecido na
fixed
instrução. Nesse caso, o inicializador calcula um ponteiro para o primeiro elemento do buffer de tamanho fixo (§23.8.3), e o buffer de tamanho fixo tem a garantia de permanecer em um endereço fixo durante a duração dafixed
instrução.
Para cada endereço calculado por um fixed_pointer_initializer a fixed
declaração garante que a variável referenciada pelo endereço não está sujeita a realocação ou descarte pelo coletor de lixo durante a vigência da fixed
declaração.
Exemplo: Se o endereço calculado por um fixed_pointer_initializer fizer referência a um campo de um objeto ou a um elemento de uma instância de matriz, a instrução fixa garante que a instância de objeto que contém não seja realocada ou descartada durante o tempo de vida da instrução. Exemplo final
É responsabilidade do programador garantir que os ponteiros criados por instruções fixas não sobrevivam além da execução dessas instruções.
Exemplo: Quando ponteiros criados por
fixed
instruções são passados para APIs externas, é responsabilidade do programador garantir que as APIs não retenham memória desses ponteiros. Exemplo final
Objetos fixos podem causar fragmentação da pilha (porque eles não podem ser movidos). Por essa razão, os objetos devem ser fixados apenas quando absolutamente necessário e, em seguida, apenas pelo menor período de tempo possível.
Exemplo: O exemplo
class Test { static int x; int y; unsafe static void F(int* p) { *p = 1; } static void Main() { Test t = new Test(); int[] a = new int[10]; unsafe { fixed (int* p = &x) F(p); fixed (int* p = &t.y) F(p); fixed (int* p = &a[0]) F(p); fixed (int* p = a) F(p); } } }
demonstra vários usos da
fixed
declaração. A primeira instrução corrige e obtém o endereço de um campo estático, a segunda instrução corrige e obtém o endereço de um campo de instância, e a terceira instrução corrige e obtém o endereço de um elemento de matriz. Em cada caso, teria sido um erro usar o operador regular&
, uma vez que as variáveis são todas classificadas como variáveis móveis.A terceira e quarta
fixed
afirmações do exemplo acima produzem resultados idênticos. Em geral, para uma instânciaa
de matriz, especificara[0]
em umafixed
instrução é o mesmo que simplesmente especificara
.Exemplo final
Em um contexto inseguro, os elementos de matriz de matrizes unidimensionais são armazenados em ordem de índice crescente, começando com índice 0
e terminando com índice Length – 1
. Para matrizes multidimensionais, os elementos da matriz são armazenados de tal forma que os índices da dimensão mais à direita são aumentados primeiro, depois a próxima dimensão esquerda e assim por diante para a esquerda.
Dentro de uma fixed
instrução que obtém um ponteiro p
para uma instância a
de matriz, os valores de ponteiro variam de p
para p + a.Length - 1
representar endereços dos elementos na matriz. Da mesma forma, as variáveis variam de para p[0]
representar os elementos reais da p[a.Length - 1]
matriz. Dada a forma como as matrizes são armazenadas, uma matriz de qualquer dimensão pode ser tratada como se fosse linear.
Exemplo:
class Test { static void Main() { int[,,] a = new int[2,3,4]; unsafe { fixed (int* p = a) { for (int i = 0; i < a.Length; ++i) // treat as linear { p[i] = i; } } } for (int i = 0; i < 2; ++i) { for (int j = 0; j < 3; ++j) { for (int k = 0; k < 4; ++k) { Console.Write($"[{i},{j},{k}] = {a[i,j,k],2} "); } Console.WriteLine(); } } } }
que produz a produção:
[0,0,0] = 0 [0,0,1] = 1 [0,0,2] = 2 [0,0,3] = 3 [0,1,0] = 4 [0,1,1] = 5 [0,1,2] = 6 [0,1,3] = 7 [0,2,0] = 8 [0,2,1] = 9 [0,2,2] = 10 [0,2,3] = 11 [1,0,0] = 12 [1,0,1] = 13 [1,0,2] = 14 [1,0,3] = 15 [1,1,0] = 16 [1,1,1] = 17 [1,1,2] = 18 [1,1,3] = 19 [1,2,0] = 20 [1,2,1] = 21 [1,2,2] = 22 [1,2,3] = 23
Exemplo final
Exemplo: No código a seguir
class Test { unsafe static void Fill(int* p, int count, int value) { for (; count != 0; count--) { *p++ = value; } } static void Main() { int[] a = new int[100]; unsafe { fixed (int* p = a) Fill(p, 100, -1); } } }
Uma
fixed
instrução é usada para corrigir uma matriz para que seu endereço possa ser passado para um método que usa um ponteiro.Exemplo final
Um char*
valor produzido pela fixação de uma ocorrência de cadeia de caracteres sempre aponta para uma cadeia de caracteres terminada em nulo. Dentro de uma instrução fixa que obtém um ponteiro p
para uma ocorrência s
de cadeia de caracteres , os valores de ponteiro variam de para p
representar endereços dos caracteres na cadeia de p + s.Length ‑ 1
caracteres e o valor p + s.Length
do ponteiro sempre aponta para um caractere nulo (o caractere com valor '\0').
Exemplo:
class Test { static string name = "xx"; unsafe static void F(char* p) { for (int i = 0; p[i] != '\0'; ++i) { System.Console.WriteLine(p[i]); } } static void Main() { unsafe { fixed (char* p = name) F(p); fixed (char* p = "xx") F(p); } } }
Exemplo final
Exemplo: O código a seguir mostra um fixed_pointer_initializer com uma expressão do tipo diferente de array_type ou
string
:public class C { private int _value; public C(int value) => _value = value; public ref int GetPinnableReference() => ref _value; } public class Test { unsafe private static void Main() { C c = new C(10); fixed (int* p = c) { // ... } } }
Type
C
tem um método acessívelGetPinnableReference
com a assinatura correta.fixed
Na instrução, oref int
retornado desse método quando ele é chamadoc
é usado para inicializar oint*
ponteirop
. Exemplo final
Modificar objetos de tipo gerenciado por meio de ponteiros fixos pode resultar em comportamento indefinido.
Nota: Por exemplo, como as cadeias de caracteres são imutáveis, é responsabilidade do programador garantir que os caracteres referenciados por um ponteiro para uma cadeia de caracteres fixa não sejam modificados. Nota final
Nota: A terminação nula automática de cadeias de caracteres é particularmente conveniente ao chamar APIs externas que esperam cadeias de caracteres "estilo C". Observe, no entanto, que uma ocorrência de cadeia de caracteres tem permissão para conter caracteres nulos. Se esses caracteres nulos estiverem presentes, a cadeia de caracteres aparecerá truncada quando tratada como terminada
char*
em nulo. Nota final
23.8 Buffers de tamanho fixo
23.8.1 Generalidades
Os buffers de tamanho fixo são usados para declarar matrizes em linha "estilo C" como membros de structs e são principalmente úteis para interface com APIs não gerenciadas.
23.8.2 Declarações de buffer de tamanho fixo
Um buffer de tamanho fixo é um membro que representa o armazenamento para um buffer de comprimento fixo de variáveis de um determinado tipo. Uma declaração de buffer de tamanho fixo introduz um ou mais buffers de tamanho fixo de um determinado tipo de elemento.
Nota: Como uma matriz, um buffer de tamanho fixo pode ser considerado como contendo elementos. Como tal, o termo tipo de elemento, conforme definido para uma matriz, também é usado com um buffer de tamanho fixo. Nota final
Os buffers de tamanho fixo só são permitidos em declarações struct e só podem ocorrer em contextos não seguros (§23.2).
fixed_size_buffer_declaration
: attributes? fixed_size_buffer_modifier* 'fixed' buffer_element_type
fixed_size_buffer_declarators ';'
;
fixed_size_buffer_modifier
: 'new'
| 'public'
| 'internal'
| 'private'
| 'unsafe'
;
buffer_element_type
: type
;
fixed_size_buffer_declarators
: fixed_size_buffer_declarator (',' fixed_size_buffer_declarator)*
;
fixed_size_buffer_declarator
: identifier '[' constant_expression ']'
;
Uma declaração de buffer de tamanho fixo pode incluir um conjunto de atributos (§22), um new
modificador (§15.3.5), modificadores de acessibilidade correspondentes a qualquer uma das acessibilidades declaradas permitidas para membros struct (§16.4.3) e um unsafe
modificador (§23.2). Os atributos e modificadores se aplicam a todos os membros declarados pela declaração de buffer de tamanho fixo. É um erro para o mesmo modificador aparecer várias vezes em uma declaração de buffer de tamanho fixo.
Uma declaração de buffer de tamanho fixo não tem permissão para incluir o static
modificador.
O tipo de elemento buffer de uma declaração de buffer de tamanho fixo especifica o tipo de elemento do(s) buffer(s) introduzido(s) pela declaração. O tipo de elemento tampão deve ser um dos tipos sbyte
predefinidos , byte
, short
, ushort
, int
, uint
, long
, ulong
, char
float
double
ou bool
.
O tipo de elemento buffer é seguido por uma lista de declaradores de buffer de tamanho fixo, cada um dos quais introduz um novo membro. Um declarador de buffer de tamanho fixo consiste em um identificador que nomeia o membro, seguido por uma expressão constante incluída e [
]
tokens. A expressão constante denota o número de elementos no membro introduzidos por esse declarador de buffer de tamanho fixo. O tipo de expressão constante deve ser implicitamente convertível em tipo int
, e o valor deve ser um número inteiro positivo diferente de zero.
Os elementos de uma memória intermédia de tamanho fixo devem ser dispostos sequencialmente na memória.
Uma declaração de buffer de tamanho fixo que declara vários buffers de tamanho fixo é equivalente a várias declarações de uma única declaração de buffer de tamanho fixo com os mesmos atributos e tipos de elementos.
Exemplo:
unsafe struct A { public fixed int x[5], y[10], z[100]; }
é equivalente a
unsafe struct A { public fixed int x[5]; public fixed int y[10]; public fixed int z[100]; }
Exemplo final
23.8.3 Buffers de tamanho fixo em expressões
A pesquisa de membros (§12.5) de um membro de buffer de tamanho fixo prossegue exatamente como a pesquisa de membros de um campo.
Um buffer de tamanho fixo pode ser referenciado em uma expressão usando um simple_name (§12.8.4), um member_access (§12.8.7) ou um element_access (§12.8.12).
Quando um membro de buffer de tamanho fixo é referenciado como um nome simples, o efeito é o mesmo que um acesso de membro do formulário this.I
, onde I
é o membro de buffer de tamanho fixo.
Em um acesso de membro do formulário E.I
onde E.
pode ser o implícito this.
, se E
é de um tipo struct e uma pesquisa de membro desse tipo struct identifica um membro de I
tamanho fixo, então E.I
é avaliado e classificado da seguinte forma:
- Se a expressão
E.I
não ocorrer em um contexto inseguro, ocorrerá um erro em tempo de compilação. - Se
E
for classificado como um valor, ocorrerá um erro em tempo de compilação. - Caso contrário, se
E
for uma variável móvel (§23.4), então:- Se a expressão
E.I
for um fixed_pointer_initializer (§23.7), o resultado da expressão será um ponteiro para o primeiro elemento do membroI
do buffer de tamanho fixo emE
. - Caso contrário, se a expressão
E.I
for um primary_no_array_creation_expression (§12.8.12.1) dentro de um element_access (§12.8.12) do formulárioE.I[J]
, então o resultado deE.I
é um ponteiro,P
, para o primeiro elemento do membroI
do buffer de tamanho fixo emE
, e o element_access anexador é então avaliado como o pointer_element_access (§23.6.4)P[J]
. - Caso contrário, ocorrerá um erro em tempo de compilação.
- Se a expressão
- Caso contrário,
E
faz referência a uma variável fixa e o resultado da expressão é um ponteiro para o primeiro elemento do membroI
do buffer de tamanho fixo emE
. O resultado é do tipoS*
, onde S é o tipo de elemento deI
, e é classificado como um valor.
Os elementos subsequentes do buffer de tamanho fixo podem ser acessados usando operações de ponteiro do primeiro elemento. Ao contrário do acesso a matrizes, o acesso aos elementos de um buffer de tamanho fixo é uma operação insegura e não é verificado intervalo.
Exemplo: O seguinte declara e usa um struct com um membro de buffer de tamanho fixo.
unsafe struct Font { public int size; public fixed char name[32]; } class Test { unsafe static void PutString(string s, char* buffer, int bufSize) { int len = s.Length; if (len > bufSize) { len = bufSize; } for (int i = 0; i < len; i++) { buffer[i] = s[i]; } for (int i = len; i < bufSize; i++) { buffer[i] = (char)0; } } unsafe static void Main() { Font f; f.size = 10; PutString("Times New Roman", f.name, 32); } }
Exemplo final
23.8.4 Verificação de atribuição definitiva
Os buffers de tamanho fixo não estão sujeitos à verificação de atribuição definida (§9.4), e os membros do buffer de tamanho fixo são ignorados para fins de verificação de atribuição definida de variáveis de tipo struct.
Quando a variável struct mais externa de um membro de buffer de tamanho fixo é uma variável estática, uma variável de instância de uma instância de classe ou um elemento de matriz, os elementos do buffer de tamanho fixo são automaticamente inicializados para seus valores padrão (§9.3). Em todos os outros casos, o conteúdo inicial de um buffer de tamanho fixo é indefinido.
23.9 Alocação de pilha
Ver §12.8.22 para obter informações gerais sobre o operador stackalloc
. Aqui, a capacidade desse operador de resultar em um ponteiro é discutida.
Quando um stackalloc_expression ocorre como a expressão de inicialização de um local_variable_declaration (§13.6.2), onde o local_variable_type é um tipo de ponteiro (§23.3) ou é inferido (var
), o resultado do stackalloc_expression é um ponteiro de tipo T*
, onde T
é o unmanaged_type do stackalloc_expression. Neste caso, o resultado é um ponteiro para o início do bloco alocado.
Exemplo:
unsafe { // Memory uninitialized int* p1 = stackalloc int[3]; // Memory initialized int* p2 = stackalloc int[3] { -10, -15, -30 }; // Type int is inferred int* p3 = stackalloc[] { 11, 12, 13 }; // Can't infer context, so pointer result assumed var p4 = stackalloc[] { 11, 12, 13 }; // Error; no conversion exists long* p5 = stackalloc[] { 11, 12, 13 }; // Converts 11 and 13, and returns long* long* p6 = stackalloc[] { 11, 12L, 13 }; // Converts all and returns long* long* p7 = stackalloc long[] { 11, 12, 13 }; }
Exemplo final
Ao contrário do acesso a matrizes ou stackalloc
blocos 'ed do Span<T>
tipo, o acesso aos elementos de um stackalloc
bloco 'ed do tipo ponteiro é uma operação insegura e não é intervalo verificado.
Exemplo: No código a seguir
class Test { static string IntToString(int value) { if (value == int.MinValue) { return "-2147483648"; } int n = value >= 0 ? value : -value; unsafe { char* buffer = stackalloc char[16]; char* p = buffer + 16; do { *--p = (char)(n % 10 + '0'); n /= 10; } while (n != 0); if (value < 0) { *--p = '-'; } return new string(p, 0, (int)(buffer + 16 - p)); } } static void Main() { Console.WriteLine(IntToString(12345)); Console.WriteLine(IntToString(-999)); } }
Uma
stackalloc
expressão é usada noIntToString
método para alocar um buffer de 16 caracteres na pilha. O buffer é automaticamente descartado quando o método retorna.Note, no entanto, que
IntToString
pode ser reescrito no modo de segurança, ou seja, sem usar ponteiros, da seguinte forma:class Test { static string IntToString(int value) { if (value == int.MinValue) { return "-2147483648"; } int n = value >= 0 ? value : -value; Span<char> buffer = stackalloc char[16]; int idx = 16; do { buffer[--idx] = (char)(n % 10 + '0'); n /= 10; } while (n != 0); if (value < 0) { buffer[--idx] = '-'; } return buffer.Slice(idx).ToString(); } }
Exemplo final
Fim do texto condicionalmente normativo.
ECMA C# draft specification