Попытка понять, как работает тип возвращаемого указателя - PullRequest
10 голосов
/ 31 декабря 2011

Я пытаюсь понять, как работает возврат указателя в следующих сценариях:

#include <iostream>
using namespace std;

// Why does this work? I can even pass the return value to another function
// and the contents do not change.
char* StringFromFunction()
{
  char* pReturn = "This string was created in the function.";
  return pReturn;
}

// I know this is wrong because the memory address where 5 is stored can be
// overwritten.
int* IntegerFromFunction()
{
  int returnValue = 5;
  return &returnValue;
}

int main()
{
  int*  pInteger;
  char* pString;

  pString = StringFromFunction();
  pInteger = IntegerFromFunction();

  cout << *pInteger << endl << pString << endl;

  return 0;
}

Вывод программы такой, какой я ожидаю:

5
This string was created in the function.

Единственное предупреждение компилятора, которое я получаю в Visual C ++ 2010 Express, это " c: \ vc2010projects \ test \ main.cpp (14): предупреждение C4172: возвращение адреса локальной переменной или временной " и только это показывает, когда я использую IntegerFromFunction(), а не StringFromFunction().

Я думаю, что из приведенных выше примеров я понимаю следующее:

Внутри StringFromFunction() выделена память для текста «Эта строка была создана в функции». происходит во время выполнения и, поскольку он является строковым литералом, содержимое сохраняется в памяти даже после возврата из функции, и поэтому указатель pString в main() может быть передан другой функции, и строка может отображаться в ней .

Однако для IntegerFromFunction(), когда функция возвращает выделенную память, она освобождается и, следовательно, этот адрес памяти может быть перезаписан.

Наверное, мои основные вопросы: можно ли безопасно передавать указатели, которые указывают на строковые литералы, по всей программе?

Ответы [ 6 ]

4 голосов
/ 31 декабря 2011

Строковые литералы на самом деле не хранятся в стеке для функции, как автоматические переменные, но хранятся в специальном месте (например, глобальные переменные).

Обратите внимание, что запись в них не переносима, поэтому онаЛучше всего использовать их как const char *, а не char *.

3 голосов
/ 31 декабря 2011

Самый простой способ увидеть разницу - сгенерировать разборку простого примера hello-world-ish:

char* test() {
 return "Test";
}

int main(int argc, char* argv[]) {
 return 0;
}

Это разборка с gcc во FreeBSD с отключенной оптимизацией

    .file   "hellow.c"
    .section    .rodata
.LC0:
    .string "test"
    .text
    .p2align 4,,15
.globl test
    .type   test, @function
test:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $.LC0, %eax
    popl    %ebp
    ret
    .size   test, .-test
    .p2align 4,,15
.globl main
    .type   main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ecx
    call    test
    movl    $0, %eax
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 4.2.1 20070719  [FreeBSD]"

Как видите, сам строковый литерал хранился в разделе .LC0, а не в самом коде. Тестовая функция просто возвращает указатель на начало .LC0 (movl $ .LC0,% eax), так как это первый строковый литерал. Расположение похожее (но не одинаковое) в зависимости от исполняемого формата, в который вы компилируете. Прочитайте A.out (текстовый сегмент) или PE .

3 голосов
/ 31 декабря 2011

Когда я компилирую вашу программу, я получаю дополнительное предупреждение от g++, мой компилятор:

$ make strings
g++     strings.cc   -o strings
strings.cc: In function ‘char* StringFromFunction()’:
strings.cc:8:19: warning: deprecated conversion from string constant to ‘char*’
strings.cc: In function ‘int* IntegerFromFunction()’:
strings.cc:16:7: warning: address of local variable ‘returnValue’ returned

Чтобы избежать предупреждения, добавьте const перед объявлением переменной, типом возвращаемого значения функции и переменной в функции main().

В цепочке инструментов GNU будет храниться строка This string was created in the function. в разделе .rodata только для чтения, которая действительна в течение всего срока жизни программы:

$ readelf -p .rodata strings

String dump of section '.rodata':
  [     8]  This string was created in the function.

Конечно, вы не можете изменять содержимое строки, но это обычно хорошо для строк, статически скомпилированных в программу.

2 голосов
/ 31 декабря 2011

Ключевым моментом, который нужно понять, является время жизни объекта, на который вы возвращаете указатель (на самом деле, время жизни объекта - это ключевая вещь, которую нужно понимать почти во всех экземплярах объекта). Стандарт C использует терминологию «длительность хранения» для времени существования объекта, поскольку в C объект буквально является областью хранения данных, которая представляет значения.

Строковый литерал имеет «статическую продолжительность хранения», что означает (C99 6.2.4 / 3):

Его время жизни - это полное выполнение программы, а его сохраненное значение инициализируется только один раз, до запуска программы.

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

Локальная переменная int returnValue в другом примере имеет «длительность автоматического хранения», что означает (C99 6.2.4 / 4):

его время жизни простирается от входа в блок, с которым он связан, до тех пор, пока выполнение этого блока не закончится каким-либо образом

(обратите внимание, что время жизни автоматического массива переменной длины немного отличается).

Таким образом, указатель на returnValue становится недействительным в тот момент, когда возвращается функция.

Я думаю, что время жизни объекта - это одна из фундаментальных вещей, которую должен понимать каждый программист, и это особенно важно в C и C ++, поскольку программист в значительной степени отвечает за правильную обработку, особенно при работе с указателями.

1 голос
/ 31 декабря 2011

Строковые литералы, такие как "This string was created in the function.", помещаются в постоянную память. Вам разрешено присваивать им char * для обратной совместимости, более правильно использовать const char *, который точно отражает их природу.

1 голос
/ 31 декабря 2011

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

/* global/static variables go in the data section at compile time, not in the stack*/
char myString[6] = {'a', ' ',  'l', 'i', 't', '\0'};

char* StringFromFunction()
{
  char* pReturn = &myString[0]; //this pointer is not actually to inside this function!
  return pReturn; 
}

Если вы хотите взорвать вещи, попробуйте выделить реальный массив в функции вместо того, чтобы получить указатель на строковый литерал.

char* StringFromFunction()
{
    char myString[6] = {'a', ' ',  'l', 'i', 't', '\0'};
    return &myString[0];
}

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

...