Pergunta

Tudo bem, eu acho que todos concordamos que o que acontece com o seguinte código é indefinido, dependendo do que for aprovada,

void deleteForMe(int* pointer)
{
     delete[] pointer;
}

O ponteiro poderia ser todos os tipos de coisas diferentes, e assim realizar uma delete[] incondicional sobre ele é indefinido. No entanto, vamos supor que estamos realmente passando um ponteiro para a matriz,

int main()
{
     int* arr = new int[5];
     deleteForMe(arr);
     return 0;
}

A minha pergunta é, neste caso, onde o ponteiro é um array, quem é que sabe disso? Quer dizer, do ponto de vista da linguagem / compilador, ele não tem idéia ou não arr é um ponteiro para a matriz contra um ponteiro para um único int. Heck, que ainda não sabe se arr foi criado dinamicamente. No entanto, se eu faço o seguinte em vez disso,

int main()
{
     int* num = new int(1);
     deleteForMe(num);
     return 0;
}

O sistema operacional é inteligente o suficiente para apenas um int de exclusão e não ir em algum tipo de 'Killing Spree' apagando o resto da memória para além desse ponto (contraste isso com strlen e uma string não terminada em \0 - lo vai continuar até que ela atinge 0).

Assim, cujo trabalho é para lembrar essas coisas? O OS manter algum tipo de registro no fundo? (Quer dizer, eu percebo que eu comecei este post dizendo que o que acontece é indefinido, mas o fato é, o cenário 'matando farra' não acontecer, assim, portanto, no mundo prático alguém é lembrando-se.)

Foi útil?

Solução

O compilador não sabe que é um array, ele está confiando o programador. A exclusão de um ponteiro para um único int com delete [] resultaria em um comportamento indefinido. Seu segundo exemplo main() não é seguro, mesmo se ele não falhar imediatamente.

O compilador tem que manter o controle de quantos objetos precisam ser excluídos de alguma forma. Pode fazer isso por excesso de alocação suficiente para armazenar o tamanho da matriz. Para mais detalhes, consulte o C ++ Super FAQ .

Outras dicas

Uma questão que as respostas dadas até agora não parecem endereço: se as bibliotecas de tempo de execução (não o OS, na verdade) pode acompanhar o número de coisas na matriz, em seguida, por que precisamos a sintaxe delete[] em tudo? Por que não pode um único formulário delete ser usado para lidar com todas as exclusões?

A resposta para isso vai voltar às raízes de C ++ como uma linguagem C-compatível (que já não realmente se esforça para ser.) A filosofia de Stroustrup foi que o programador não deve ter que pagar por quaisquer características que eles não estão usando. Se eles não estão usando matrizes, então eles não deveriam ter que carregar o custo de arranjos de objetos para cada pedaço de memória alocado.

Isto é, se o seu código simplesmente faz

Foo* foo = new Foo;

então o espaço de memória que está alocado para foo não deve incluir qualquer sobrecarga extra que seria necessário para matrizes de suporte de Foo.

Uma vez que apenas as alocações de matriz são configurados para transportar as informações tamanho da matriz extra, então você precisa para contar as bibliotecas de execução para procurar essa informação quando você excluir os objetos. É por isso que precisamos usar

delete[] bar;

em vez de apenas

delete bar;

se a barra é um ponteiro para uma matriz.

Para a maioria de nós (eu incluído), que pieguice sobre alguns bytes extras de memória parece pitorescas estes dias. Mas ainda existem algumas situações em que salvar alguns bytes (de que poderia ser um número muito elevado de blocos de memória) pode ser importante.

Sim, o sistema operacional mantém algumas coisas no 'fundo'. Por exemplo, se você executar

int* num = new int[5];

o sistema operacional pode alocar 4 bytes extras, armazenar o tamanho da alocação nos primeiros 4 bytes de memória alocada e retornar um ponteiro de deslocamento (ou seja, ele aloca espaços de memória 1000 para 1024, mas o ponteiro retornado pontos para 1004, com locais 1000-1003 armazenar o tamanho da alocação). Então, quando delete é chamado, ele pode olhar para 4 bytes antes do ponteiro passado para ele para encontrar o tamanho da alocação.

Estou certo de que há outras maneiras de rastrear o tamanho de uma atribuição, mas isso é uma opção.

Isto é muito semelhante ao este questão e tem muitos detalhes a sua procura.

Mas basta dizer, não é o trabalho do sistema operacional para controlar nada disso. É realmente as bibliotecas de tempo de execução ou o gerenciador de memória subjacente que irá controlar o tamanho da matriz. Isso geralmente é feito através da atribuição de memória extra na frente e armazenar o tamanho da matriz nesse local (a maioria usa um nó principal).

Esta é visível em algumas implementações, executando o código a seguir

int* pArray = new int[5];
int size = *(pArray-1);

delete ou delete[] provavelmente iria libertar tanto a memória alocada (memória apontada), mas a grande diferença é que delete em uma matriz não vai chamar o destruidor de cada elemento da matriz.

De qualquer forma, misturando new/new[] e delete/delete[] é provavelmente UB.

Ele não sabe que é um array, é por isso que você tem que delete[] vez de delete regular de idade alimentação.

Eu tinha uma pergunta semelhante a este. Em C, você alocar memória com malloc () (ou outra função similar), e excluí-lo com free (). Existe apenas um malloc (), que simplesmente atribui um certo número de bytes. Há apenas um free (), que simplesmente leva um ponteiro como parâmetro a sua.

Então, por que é que em C você pode simplesmente entregar o ponteiro para livre, mas em C ++ deve dizer-lhe se é uma matriz ou uma única variável?

A resposta, eu aprendi, tem a ver com destruidores de classe.

Se você alocar uma instância de uma classe MyClass ...

classes = new MyClass[3];

e excluí-lo com delete, você só pode obter o destruidor para a primeira instância de MyClass chamado. Se você usar excluir [], você pode ter certeza que o destruidor será chamado para todas as instâncias na matriz.

Esta é a diferença importante. Se você está simplesmente trabalhando com tipos padrão (por exemplo, int) você realmente não vai ver esta questão. Além disso, você deve se lembrar que o comportamento para a utilização de exclusão no novo [] e delete [] no novo é indefinido -. Ele pode não funcionar da mesma maneira em todo compilador / sistema

É até o tempo de execução que é responsável pela alocação de memória, da mesma forma que você pode excluir uma matriz criada com malloc em C padrão usando livre. Eu acho que cada um compilador implementa-lo de forma diferente. Uma forma comum consiste em atribuir uma célula adicional para o tamanho da matriz.

No entanto, o tempo de execução não é inteligente o suficiente para detectar se é ou não é um array ou um ponteiro, você tem que informá-la, e se você está enganado, você não quer excluir corretamente (Por exemplo, ptr em vez de gama ), ou você acabar tendo um valor relacionado para o tamanho e causar danos significativos.

Uma das abordagens para compiladores é alocar a memória e armazenamento de contagem de pouco mais de elementos no elemento de cabeça.

Exemplo de como isso poderia ser feito: Aqui

int* i = new int[4];

compilador irá alocar sizeof (int) * 5 bytes.

int *temp = malloc(sizeof(int)*5)

Será armazenar 4 no primeiro sizeof(int) bytes

*temp = 4;

e set i

i = temp + 1;

Assim i aponta para matriz de 4 elementos, não 5.

E

delete[] i;

será processado seguinte maneira

int *temp = i - 1;
int numbers_of_element = *temp; // = 4
... call destructor for numbers_of_element elements if needed
... that are stored in temp + 1, temp + 2, ... temp + 4
free (temp)

Semanticamente, ambas as versões do operador delete em C ++ pode "comer" qualquer ponteiro; No entanto, se um ponteiro para um único objeto é dado a delete[], em seguida, UB vai resultar, ou seja, qualquer coisa pode acontecer, incluindo uma falha no sistema ou nada.

C ++ requer que o programador escolher a versão apropriada do operador delete dependendo do objecto de deallocation:. Array ou objeto único

Se o compilador pode determinar automaticamente se um ponteiro passado para o operador de exclusão foi uma matriz de ponteiro, em seguida, haveria apenas um operador de exclusão em C ++, o que seria suficiente para ambos os casos.

Concorda que o compilador não sei se é um array ou não. Cabe ao programador.

O compilador, por vezes, manter o controle de quantos objetos precisam ser excluídos por excesso de alocação suficiente para armazenar o tamanho da matriz, mas nem sempre necessário.

Para uma especificação completa quando o armazenamento extra é alocado, consulte C ++ ABI (como compiladores são implementadas): Itanium C ++ ABI: array operador novos cookies

Você não pode usar Excluir para uma matriz e você não pode usar Excluir [] para um não-matriz.

"comportamento indefinido" significa simplesmente a especificação linguagem não faz gaurantees quanto ao que vai acontecer. Não nessacerally significa que algo ruim vai acontecer.

Assim, cujo trabalho é para lembrar essas coisas? O OS manter algum tipo de registro no fundo? (Quer dizer, eu percebo que eu comecei este post dizendo que o que acontece é indefinido, mas o fato é, o cenário 'matando farra' não acontecer, assim, portanto, no mundo prático alguém está lembrando.)

Há tipicamente duas camadas aqui. O gerenciador de memória subjacente ea implementação C ++.

Em geral, o gerenciador de memória vai se lembrar (entre outras coisas) o tamanho do bloco de memória que foi alocada. Isso pode ser maior do que o bloco a implementação C ++ pediu. Normalmente, o gerenciador de memória irá armazená-lo de metadados antes do bloco de memória alocado.

A implementação C ++ geralmente só se lembra do tamanho da matriz se ele precisa fazer isso para ele é próprios fins, geralmente porque o tipo tem um destrutor não-trivial.

Assim, para tipos com um destruidor trivial a implementação de "apagar" e "delete []" é tipicamente o mesmo. A implementação C ++ simplesmente passa o ponteiro para o gerenciador de memória subjacente. Algo como

free(p)

Por outro lado para os tipos com um destrutor não-trivial "delete" e "delete []" são susceptíveis de ser diferente. "Delete" seria somthing como (onde T é o tipo que o ponteiro aponta a)

p->~T();
free(p);

Enquanto "delete []" seria algo como.

size_t * pcount = ((size_t *)p)-1;
size_t count = *count;
for (size_t i=0;i<count;i++) {
  p[i].~T();
}
char * pmemblock = ((char *)p) - max(sizeof(size_t),alignof(T));
free(pmemblock);

Ei bem ho isso depende do que você alocar com nova expressão [] quando você alocar variedade de construção em espécie ou classe / estrutura e você não fornecer o seu construtor e destruidor o operador irá tratá-lo como um tamanho "sizeof ( objeto) * numObjects" em vez de matriz de objeto, portanto, este número caso de objetos alocados não será armazenada em qualquer lugar, no entanto, se você alocar matriz de objeto e você fornecer construtor e destruidor em seu objeto de mudança de comportamento, nova expressão alocará 4 bytes mais e número da loja de objetos em primeiros 4 bytes para que o processo de destruição para cada um deles pode ser chamado e, portanto, nova expressão [] retornará ponteiro desviado por 4 bytes para a frente, do que quando a memória é devolvida a exclusão [] expressão vai chamar um modelo de função primeiro, iterate através matriz de objetos e destruidor de chamada para cada um deles. Eu criei este código simples sobrecargas bruxas new [] e delete [] expressões e fornece uma função de modelo para desalocar memória e destructor chamada para cada objeto, se necessário:

// overloaded new expression 
void* operator new[]( size_t size )
{
    // allocate 4 bytes more see comment below 
    int* ptr = (int*)malloc( size + 4 );

    // set value stored at address to 0 
    // and shift pointer by 4 bytes to avoid situation that
    // might arise where two memory blocks 
    // are adjacent and non-zero
    *ptr = 0;
    ++ptr; 

    return ptr;
}
//////////////////////////////////////////

// overloaded delete expression 
void static operator delete[]( void* ptr )
{
    // decrement value of pointer to get the
    // "Real Pointer Value"
    int* realPtr = (int*)ptr;
    --realPtr;

    free( realPtr );
}
//////////////////////////////////////////

// Template used to call destructor if needed 
// and call appropriate delete 
template<class T>
void Deallocate( T* ptr )
{
    int* instanceCount = (int*)ptr;
    --instanceCount;

    if(*instanceCount > 0) // if larger than 0 array is being deleted
    {
        // call destructor for each object
        for(int i = 0; i < *instanceCount; i++)
        {
            ptr[i].~T();
        }
        // call delete passing instance count witch points
        // to begin of array memory 
        ::operator delete[]( instanceCount );
    }
    else
    {
        // single instance deleted call destructor
        // and delete passing ptr
        ptr->~T();
        ::operator delete[]( ptr );
    }
}

// Replace calls to new and delete
#define MyNew ::new
#define MyDelete(ptr) Deallocate(ptr)

// structure with constructor/ destructor
struct StructureOne
{
    StructureOne():
    someInt(0)
    {}
    ~StructureOne() 
    {
        someInt = 0;
    }

    int someInt;
};
//////////////////////////////

// structure without constructor/ destructor
struct StructureTwo
{
    int someInt;
};
//////////////////////////////


void main(void)
{
    const unsigned int numElements = 30;

    StructureOne* structOne = nullptr;
    StructureTwo* structTwo = nullptr;
    int* basicType = nullptr;
    size_t ArraySize = 0;

/**********************************************************************/
    // basic type array 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( int ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor. value assigned to basicType pointer
    // will be the same as value of "++ptr" in new expression
    basicType = MyNew int[numElements];

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( int ) * numElements"
    MyDelete( basicType );

/**********************************************************************/
    // structure without constructor and destructor array 

    // behavior will be the same as with basic type 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( StructureTwo ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor value assigned to structTwo pointer
    // will be the same as value of "++ptr" in new expression
    structTwo = MyNew StructureTwo[numElements]; 

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( StructureTwo ) * numElements"
    MyDelete( structTwo );

/**********************************************************************/
    // structure with constructor and destructor array 

    // place break point check size and compare it with size passed in
    // new expression size in expression will be larger by 4 bytes
    ArraySize = sizeof( StructureOne ) * numElements;

    // value assigned to "structOne pointer" will be different 
    // of "++ptr" in new expression  "shifted by another 4 bytes"
    structOne = MyNew StructureOne[numElements];

    // Place break point in template function to see the behavior
    // destructors will be called for each array object 
    MyDelete( structOne );
}
///////////////////////////////////////////

apenas definir um destruidor dentro de uma classe e executar o código com ambos sintaxe

delete pointer

delete [] pointer

de acordo com a saída u pode encontrar as soluções

A resposta:

int * PArray = new int [5];

tamanho int = * (PArray-1);

Postado acima não é correta e produz valor inválido. Os "-1" elementos contagens Em 64 bits do Windows OS reside o tamanho do buffer corretos em Ptr - 4 bytes endereço

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top