Что делает указатель внутри цикла при повторной инициализации? - PullRequest
2 голосов
/ 15 апреля 2019

Я работаю с контроллерами STM32, используя встроенный инструментарий GNU ARM. Я пытаюсь выяснить, что происходит, когда я инициализирую указатель внутри цикла. Довольно минимальным примером будет следующий (частично псевдокод):

while(1)
{
    char* msg = "my message";
    transmit_via_uart(msg, strlen(msg));
    delay(1000);
}

Распределяет ли процессор новое место для строки в куче каждый раз, когда указатель msg снова инициализируется? Или он перезаписывает пространство, на которое указывал «старый» указатель msg (новое место не выделено)?

Я знаю, что мог бы поместить строку инициализации над циклом while, мне просто любопытно, что происходит, я не могу понять это.

Спасибо за быстрый ответ! Т.

РЕДАКТИРОВАТЬ: Извините! Конечно, компилятор ничего не выделяет ...:)

Ответы [ 4 ]

3 голосов
/ 15 апреля 2019
Программы

C никогда не размещаются в куче, если вы явно не используете семейство функций malloc.

Строковый литерал "my message" хранится в ПЗУ (вероятно, в разделе с именем .rodata или .text).на большинстве систем).Он выделяется при запуске программы.

Указатель msg просто указывает на этот адрес в ПЗУ.Сам указатель размещается в стеке или в регистре ЦП.

Однако компилятор достаточно умен, чтобы видеть, что адрес не меняется, даже если вы вызываете его неоднократно в цикле.Таким образом, он, скорее всего, оптимизирует переменную msg и просто передаст функции сырой, жестко запрограммированный адрес ПЗУ, где находится строка.

Вы можете поместить инициализацию над циклом просто отлично, если вы не используете каменный век, 30-летний компилятор C90.


В качестве примечания, лучший способ написать код был бы:

char msg[] = "my message";
transmit_via_uart(msg, sizeof(msg)-1);

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

3 голосов
/ 15 апреля 2019

Каждый компилятор выделяет новое место для строки в куче время, когда указатель msg снова инициализируется? Или перезаписать пространство, на которое указывал «старый» указатель (новое место не выделено)?

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

Вот как выглядит ваш код ( источник здесь ) после компиляции с полной оптимизацией, т.е. -O3 :

.LC0:
        .string "my message"
ff():
        sub     rsp, 8
.L2:
        mov     esi, 10
        mov     edi, OFFSET FLAT:.LC0
        call    transmit_via_uart(char const*, int)
        mov     edi, 1000
        call    delay(unsigned int)
        jmp     .L2

Единственное, что инициализируется в каждом цикле (.L02 раздел), - это указатель, который получает адрес известного фрагмента памяти в .LC0 с инструкцией: mov edi, OFFSET FLAT:.LC0.

Память не выделяется динамически, и если вы думаете об этом, зачем беспокоиться, если вся необходимая информация известна во время компиляции?

2 голосов
/ 15 апреля 2019

В C все строковые литералы на самом деле являются массивами символов, предназначенными только для чтения, конечно же, массивами, включая терминатор нуля.Когда вы получаете указатель на такую ​​строку, вы получаете указатель на ее первый элемент, первый символ в строке.

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

Что касается самой переменной msg, вполне вероятно, что компилятор выделит для нее место при вызове функции вместе со всеми другими локальными переменными внутри функции.Пространство для переменной может быть неинициализировано, пока вы не попадете в цикл.Тогда хороший компилятор оптимизирует его так, что переменная инициализируется только один раз.

0 голосов
/ 15 апреля 2019

Выделяет ли процессор новое место для строки в куче каждый раз, когда указатель msg инициализируется снова?

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

Типичный способ, которым компиляторы делают это, помещает строку в раздел с постоянными данными программы.

Или делаетон перезаписывает пространство, на которое указывал «старый» указатель msg (новое пространство не выделено)?

В семантике C каждый раз, когда достигается определение msg, объект с именем msgсоздается и инициализируется так, чтобы указывать на массив символов.

На практике хорошие компиляторы, особенно при включенной оптимизации, распознают, что это не является необходимым для достижения конечных эффектов вашего исходного кода.Для вызова transmit_via_uart(msg, strlen(msg)); хороший компилятор будет знать как значение msg (относительно раздела программы, в котором хранится строка), так и значение strlen(msg), и он будет генерировать инструкции для передачи этих значений transmit_via_uart без необходимости использовать фактическое хранилище для msg объекта.

Можно сделать несколько более очевидным значение для компилятора с помощью:

while(1)
{
    static const char msg[] = "my message";
    transmit_via_uart(msg, sizeof msg - 1);
    delay(1000);
}

Объявление msg какstatic и const явно указывают компилятору, что msg - это постоянный массив неизменяемых данных, а использование sizeof сообщает компилятору, что значение является фиксированным свойством объекта, а не чем-то, что может быть вычислено во время выполнения.время с strlen (хотя это все еще технически выражение времени выполнения, а не константа времени компиляции).Низкокачественный компилятор, который не смог оптимизировать исходный код, мог бы лучше работать с этим кодом.

...