2014-09-12 7 views
3

указателей действительно сложная вещь в С. Для многих людей трудно понять, так что для хорошего понимания я написал следующий код:Понимания указателей с таНос и свободными

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

int main(int argc, char *argv[]) 
{ 
    int *p; // pointer -> will be dynamic allocated 
    int *a; // array -> will be dynamic allocated 

    // print before allocate memory (1) 
    printf("&p: %p\tp: %p\t*p: %d\n", &p, p, *p); 
    printf("&a: %p\ta: %p\t*a: %d\n", &a, a, *a); 
    printf("\n"); 

    // allocate memory (2) 
    p = (int *)malloc(sizeof(int)); 
    a = (int *)malloc(sizeof(int) * 10); 

    // print after allocate, but before give a value to poinetrs (3) 
    printf("&p: %p\tp: %p\t*p: %d\n", &p, p, *p); 
    printf("&a: %p\ta: %p\t*a: %d\n", &a, a, *a); 
    printf("\n"); 

    // give a value to poinetrs (4) 
    *p = 1; 
    for (int i = 0; i < 10; i++) { a[i] = i; } 

    // print after we gave a value to pointers (5) 
    printf("&p: %p\tp: %p\t*p: %d\n", &p, p, *p); 
    printf("&a: %p\ta: %p\t*a: ", &a, a); 
    // because a is an array we must use a loop for print 
    for (int i = 0; i < 10; i++) { printf("%d ", a[i]); } 
    printf("\n"); 
    printf("\n"); 

    // free pointers (6) 
    free(p); 
    free(a); 

    // print pointers after free (7) 
    printf("&p: %p\tp: %p\t*p: %d\n", &p, p, *p); 
    printf("&a: %p\ta: %p\t*a: ", &a, a); 
    // because a is an array we must use a loop for print 
    for (int i = 0; i < 10; i++) { printf("%d ", a[i]); } 
    printf("\n"); 
    printf("\n"); 

    // try to change values after free (8) 
    *p = 12; 
    for (int i = 0; i < 10; i++) { a[i] = 3; } 

    // print after (8) 
    printf("&p: %p\tp: %p\t*p: %d\n", &p, p, *p); 
    printf("&a: %p\ta: %p\t*a: ", &a, a); 
    // because a is an array we must use a loop for print 
    for (int i = 0; i < 10; i++) { printf("%d ", a[i]); } 
    printf("\n"); 
    printf("\n"); 

    return 0; 
} 

Выход:

&p: 0xbfe5db64 p: 0xbfe5dc24 *p: -1075452506 
&a: 0xbfe5db68 a: 0xbfe5dc2c *a: -1075452502 

&p: 0xbfe5db64 p: 0x8716008 *p: 0 
&a: 0xbfe5db68 a: 0x8716018 *a: 0 

&p: 0xbfe5db64 p: 0x8716008 *p: 1 
&a: 0xbfe5db68 a: 0x8716018 *a: 0 1 2 3 4 5 6 7 8 9 

&p: 0xbfe5db64 p: 0x8716008 *p: 0 
&a: 0xbfe5db68 a: 0x8716018 *a: 0 1 2 3 4 5 6 7 8 9 

&p: 0xbfe5db64 p: 0x8716008 *p: 12 
&a: 0xbfe5db68 a: 0x8716018 *a: 3 3 3 3 3 3 3 3 3 3 

Теперь, вопросы и замечания:

  1. При печати указателей, прежде чем дать для него память, почему указатель имеет случайное значение и случайный адрес, чтобы указать на него и почему он не является NULL?

  2. После использования malloc мы можем видеть адрес, в котором указатель указывает на изменение, и его значение равно NULL, так что же делает malloc?

  3. После того, как мы придаем ему значение и печатаем его, мы освобождаем его и печатаем его снова, но значения и адрес такие же, как и для массива, но не для целого числа, почему? Так что же на самом деле бесплатно?

  4. После освобождения места мы можем продолжать изменять значения массива и целого числа, почему это возможно после свободного пространства? Нам не нужно повторно использовать malloc?

+2

«Указатели - действительно сложная штука в C». Сначала да. Но прочитайте хорошую книгу (K & R) и ** не ** продвигайтесь ни к чему другому, пока они не станут второй натурой. – Bathsheba

+0

Прежде всего, [в C вы не должны бросать возврат 'malloc'] (http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc). И чтобы продолжить, вы используете [* undefined behavior *] (http://en.wikipedia.org/wiki/Undefined_behavior) в своем коде, так как используете неинициализированные переменные. Вам повезло, что программа не сработала при первом вызове 'printf'. –

+0

Что касается неинициализированного поведения, неинициализированные нестатические локальные переменные имеют неопределенное значение (это будет казаться случайным), а использование неинициализированной нестатической локальной переменной приведет к [неопределенному поведению] (http://en.wikipedia.org/ вики/Undefined_behavior). –

ответ

5
  1. Поскольку спецификация языка говорит так. Значение указателя (т. Е. Адрес, на который он указывает) является неопределенным. Он может указывать в любом месте, точно так же, как int может содержать любое значение. Чтение этих значений (как и в случае с *p и *a в первом printf s) на самом деле не определено поведение.

  2. Если вы имеете в виду данные, которые он указывает, это 0, то есть случайно. Выделенная память не должна быть обнулена. Например, это может быть частью блока, выделенного ранее с malloc, а затем free д (free не обнулять память, смотри пункт 3. ниже.)

  3. Это также случайно. Когда вы загружаете free, он не обнуляется, и его не нужно использовать немедленно. Он может хранить старые значения до тех пор, пока он не будет использован для чего-то другого (например, другим распределением)

  4. Это также не определено поведение. Вы пишете в память, которой больше не владеете. Все может случиться. Возможно, программа разбилась. Случайно кажется, что вы можете успешно записать в массив, вероятно, потому что память по-прежнему не используется ничем другим, что могло бы вызвать более очевидную ошибку времени выполнения.

2

1.При напечатать указатели, прежде чем дать для него память, почему указатель имеют случайное значение и случайный адрес, чтобы указать на него, и почему это не NULL?

Вы не указали NULL. вы просто заявляете об этом. После объявления указателя оно может иметь любое значение.

Чтобы нуль-

int *p = NULL; 
int *a = NULL; 

2.После мы используем таНос, мы можем увидеть адрес, где указатель указывает на изменился и его значение равно NULL, так, что таНос действительно?

Человек Page says-

void *malloc(size_t size); 

malloc() функция выделяет size байт и возвращает указатель на выделенную память. Память не инициализируется. Если размер равен 0, то malloc() возвращает либо NULL, либо уникальное значение указателя, которое впоследствии может быть успешно передано free().

Если ваша выделенная память имеет 0, это означает только случайность! Память, выделенная malloc, не освобождается. Но calloc делает!

3.Если мы даем ему значение и печатаем его, мы освобождаем его и печатаем его снова, но значения и адрес такие же, как и для массива, но не для целого числа, почему? Так что же на самом деле бесплатно?

free не означает, что он фактически удалит память! Он сообщит ОС, что я больше не хочу эту память, использовать ее для какого-то другого процесса!

Вы, безусловно, можете продолжать использовать память a после звонка free(...), и ничто вас не остановит. Однако результаты будут полностью неопределенными и непредсказуемыми. Это работает только удачей. Это общая ошибка программирования под названием «use after free», которая работает во многих программах буквально лет без «проблем» - пока это не вызовет проблему.

4. После освобождения места мы можем продолжать изменять значения массива и целого числа, почему это возможно после свободного пространства? Нам не нужно повторно использовать malloc?

Это полностью неопределенное поведение! После освобождения памяти указатель по-прежнему указывает на то же место в памяти. Он называется Dangling Pointer.

Чтобы избежать обвинчивания указателя, сделайте указатель на null после free!

Но после освобождения памяти вам необходимо использовать память, используя malloc, чтобы выделить память и использовать ее!

2

Какие указатели

заявления Pointer выглядят так же, как другие декларации: а не вводить в заблуждение. Когда указатели объявлены, ключевое слово в начале объявляет тип переменной, на которую указывает указатель. Сам указатель не относится к этому типу, он имеет тип указателя на этот тип. Указанный указатель указывает только на один конкретный тип, а не на все возможные типы. На практике все указатели рассматриваются как целые числа, но компилятор, скорее всего, будет жаловаться на назначение между несовместимыми типами.

int *p; 

указатель р не был присвоен адрес, поэтому он все еще содержит все, что случайное значение было в памяти она занимает (любое значение было до того, как был использован для р).Таким образом, если указатель содержит адрес чего-то, обозначение * p эквивалентно присвоению имени чего-то непосредственно. Какую пользу мы получаем от всего этого? Ну, сразу же он обходит ограничение по функциям. Представьте себе функцию, которая должна вернуть, скажем, два целых числа, представляющих месяц и день в течение этого месяца.

Резюме

  • Массивы всегда индекс с нулевого конца истории.

  • Нет многомерных массивов; вместо этого вы используете массивы массивов.

  • Указатели указывают на вещи; указатели на разные типы сами по себе различны.

  • Они не имеют ничего общего друг с другом или с любыми другими типами в C; автоматических преобразований между указателями и другими типами нет.

  • Указатели могут использоваться для имитации «вызова по ссылке» на функции, но для этого требуется небольшая работа.

  • Приращение или добавление чего-либо к указателю может использоваться для перемещения по массивам. Чтобы облегчить доступ к массиву с помощью инкрементных указателей, стандарт гарантирует, что в массиве n элементов, хотя элемент n не существует, использование его адреса не является ошибкой.

Арифметика указателей

Вы можете не только добавить целочисленное значение указателя, но вы можете также сравнить или вычитать два указателя одного и того же типа. Они должны указывать на один и тот же массив, или результат не определен. Разница между двумя указателями определяется как количество элементов массива, разделяющих их; тип этой разности определяется реализацией и будет одним из коротких, int или long. В следующем примере показано, как разность может быть рассчитана и использована, но прежде чем вы ее прочитаете, вам нужно знать важный момент.

В выражении имя массива преобразуется в указатель на первый элемент массива. Единственные места, где это неверно, - это когда имя массива используется вместе с sizeof, когда строка используется для инициализации массива или когда имя массива является объектом адреса оператора (унарный &). Мы еще не видели ни одного из этих случаев, они будут обсуждаться позже. Вот пример.

#include <stdio.h> 
#include <stdlib.h> 
#define ARSZ 10 

main(){ 
    float fa[ARSZ], *fp1, *fp2; 

    fp1 = fp2 = fa; /* address of first element */ 
    while(fp2 != &fa[ARSZ]){ 
      printf("Difference: %d\n", (int)(fp2-fp1)); 
      fp2++; 
    } 
    exit(EXIT_SUCCESS); 
} 

освободил Pointers

Вызов бесплатно() на указатель не изменяет его, только помечает память как свободный. Ваш указатель будет по-прежнему указывать на то же место, которое будет содержать одно и то же значение, но теперь vluae может быть перезаписано в любое время, поэтому вам не следует использовать указатель после его освобождения. Чтобы убедиться, что это хорошая идея, всегда устанавливайте указатель на NULL после его освобождения.

Смежные вопросы