Почему эта функция C имеет побочные эффекты? - PullRequest
0 голосов
/ 10 мая 2018

Может ли кто-нибудь помочь мне понять, что не так с этой программой на C?

#include <stdio.h>
#include <stdlib.h>
struct Box {
    int **value;
};
void nop(void) {
    /* Why does this function have side effects? */
    void *a = malloc(sizeof *a);
    free(a);
}
struct Box *makeBox(void) {
    int *value = NULL;
    struct Box *box = malloc(sizeof *box);
    box->value = &value;
    return box;
}
int main(void) {
    struct Box *box = makeBox();
    printf("Dereferenced: %p\n", *box->value);
    nop();
    printf("Dereferenced: %p\n", *box->value);
}

Если я запускаю его, он печатает:

Dereferenced: (nil)
Dereferenced: 0x562831863727

Однако, если я закомментирую функцию nop, я получу:

Dereferenced: (nil)
Dereferenced: (nil)

Может ли кто-нибудь помочь мне понять, почему вызов nop меняет *box->value?

Ответы [ 3 ]

0 голосов
/ 10 мая 2018

В основном функция nop() имеет "побочные эффекты" просто в результате использования стека. Проблема в функции makeBox(), которая устанавливает указатель на переменную стека. Я добавил несколько комментариев во фрагмент:

struct Box *makeBox(void) {
    // value is an integer pointer on the stack
    int *value = NULL;
    struct Box *box = malloc(sizeof *box);
    // box->value is set to the address of the stack location of value
    box->value = &value;
    return box;
}

Когда nop() выделяет a в стеке, он фактически топает в стеке, где находился ваш другой указатель. Это пример того, почему вы не можете вернуть указатель на переменную стека, поскольку он не сохраняется вне области действия функции, в которой он размещен.

0 голосов
/ 10 мая 2018

используя ваш опубликованный код (немного переформатирован для удобства чтения:

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

struct Box
{
    int **value;
};


void nop(void)
{
    /* Why does this function have side effects? */
    void *a = malloc(sizeof *a);
    free(a);
}


struct Box *makeBox(void)
{
    int *value = NULL;
    struct Box *box = malloc(sizeof *box);
    box->value = &value;
    return box;
}


int main(void)
{
    struct Box *box = makeBox();
    printf("Dereferenced: %p\n", *box->value);
    nop();
    printf("Dereferenced: %p\n", *box->value);
}

всегда включать предупреждения при компиляции. затем исправьте эти предупреждения. (для gcc, при минимальном использовании:

-Wall -Wextra -Wconversion -pedantic -std=gnu11

В результате компилятор выдаст несколько предупреждающих сообщений.

(то, что в сообщении говорится «предупреждение», а не «ошибка», не означает, что предупреждения можно игнорировать.

Предупреждения:

  1. : 13: 29: предупреждение: недопустимое применение размера sizeof к типу пустоты [-Wpointer-arith]

  2. : 30: 12: предупреждение: формат «% p» ожидает аргумент типа "Void *", но аргумент 2 имеет тип "int *" [-Wformat =]

  3. : 32: 12: предупреждение: формат «% p» ожидает аргумент типа «void *», но аргумент 2 имеет тип «int *» [-Wformat =]

EDIT: Функция makebox() возвращает адрес локальной переменной. локальные переменные выходят из области видимости при выходе из функции. так что это неопределенное поведение.

Вышеуказанное неопределенное поведение является проблемой при вызове функции: nop(). потому что он использует ту же область стека, что и в функции 'makebox () `.

Локальная переменная: void *a использует ту же часть стека, что и функциональная переменная makebox() value.

, поэтому, когда функция main() разыменовывает поле в struct Box, which points to the same area on the stack, and that area has been modified by the later call to nop () `, тогда значение в стеке в этой области было изменено.

, поэтому вызовы на printf() при первом вызове получат исходное значение, а при втором вызове получит адрес, возвращенный после вызова на malloc()

т.е. изменить код, чтобы он не имел неопределенного поведения. Это можно сделать, либо изменив struct box, чтобы он содержал фактические данные, либо переместив локальную переменную: value в некоторую «безопасную область», например, предоставив ей область видимости файла, возможно, добавив модификатор static перед объявление переменной

0 голосов
/ 10 мая 2018

Ваша программа имеет неопределенное поведение. box->value указатель внутри main содержит неопределенное значение. Внутри makeBox раньше она указывала на локальную переменную value, но теперь makeBox завершена, и эта локальная переменная исчезла навсегда. Попытка разыменовать его как *box->value приводит к неопределенному поведению. Это то, что вы наблюдаете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...