Хобрук: Ваш путь к мастерству в программировании

Переменная внутри цикла for не инициализируется повторно на каждой итерации?

У меня есть проект C++/CLI, который объявляет переменную String^ внутри цикла for, но не инициализирует ее. На первой итерации переменной присваивается некоторое значение. На каждой последующей итерации он сохраняет предыдущее значение. Не должна ли переменная в локальной области инициализироваться нулевым (или эквивалентным) значением каждый раз в цикле? Это происходит и с int. Кроме того, компилятор не предупреждает о потенциально неинициализированном значении, если я не устанавливаю уровень предупреждения на W4, и даже тогда он предупреждает только для int, а не для String^.

Это пример кода, который показывает поведение.

#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
    for(int n = 0; n < 10; n++)
    {
        String^ variable;
        int x;

        switch(n)
        {
        case 1:
            variable = "One";
            x = 1;
            break;
        case 5:
            variable = "Five";
            x = 5;
            break;
        }

        Console::WriteLine("{0}{1}", variable, x);
    }
}

Результат этого будет

One, 1
One, 1
One, 1
One, 1
Five, 5
Five, 5
Five, 5
Five, 5
Five, 5

Я совершенно неправильно понимаю, как должны быть инициализированы переменные с локальной областью? Является ли эта «функция» уникальной для управляемого С++? Если я преобразую это в С#, компилятор предупредит об обеих переменных даже на базовом уровне предупреждения.

14.12.2012

Ответы:


1

Отказ от ответственности: я довольно хорошо знаю C и C++; С++/CLI, не так уж и много. Но поведение, которое вы видите, по существу такое же, как и у аналогичной программы на C или C++.

String^ — это дескриптор для String, аналогичный указателю в C или C++.

Если C++/CLI не добавит новых правил для инициализации дескрипторов, переменная области блока типа String^ без явной инициализации изначально будет иметь значение мусора, состоящее из того, что случилось в этом куске памяти.

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

Таким образом, на первой итерации вашего цикла variable устанавливается на "One" (или, скорее, на дескриптор, который ссылается на "One"), это значение, напечатанное Console::WriteLine. Нет проблем.

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

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

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

А почему компилятор не предупреждает об этом, я не знаю. Я не решаюсь предложить ошибку компилятора, но это может быть так.

Кроме того, даже при включенных высоких уровнях предупреждений предупреждение о неинициализированных переменных требует анализа потока управления, который может не выполняться по умолчанию. Включение обоих предупреждений и высокого уровня оптимизации может дать компилятору достаточно информации, чтобы предупредить как об variable, так и x.

Все еще кажется странным, что он предупреждает о x, а не о variable с W4.

14.12.2012

2

C++/CLI — это всего лишь расширение/надмножество стандартного C++, поэтому он соответствует большинству его спецификаций, расширяя его только для соответствия требованиям CLI (~.Net).

Не должна ли переменная в локальной области инициализироваться нулевым (или эквивалентным) значением каждый раз в цикле?

AFAIK стандарт С++ не определяет способ инициализации переменных локального цикла.

Таким образом, чтобы избежать каких-либо накладных расходов, компиляторы обычно не используют специальное управление локальной памятью для циклов: см. этот вопрос SO: Есть ли накладные расходы на объявление переменной внутри цикла? (С++)

Я совершенно неправильно понимаю, как должны быть инициализированы переменные с локальной областью?

Является ли эта функция уникальной для управляемого С++?

Так что нет, это не функция или особое поведение: ваш компилятор C++/CLI использует только стандартные методы C++.

Если я преобразую это в С#, компилятор предупредит об обеих переменных даже на базовом уровне предупреждения.

С# и AFAIK Java изо всех сил стараются избежать неопределенного поведения, поэтому они заставляют вас инициализировать локальные переменные перед их использованием.

Вот CIL, полученный в результате компиляции (я немного отформатировал и прокомментировал эту кучу текста:)):

.locals init (int32 V_0, int32 V_1, string V_2, int32 V_3)
//                   ^          ^           ^          ^
//                   n          x        variable     tmp

// initialization of "n"
IL_0000:  ldc.i4.0
IL_0001:  stloc.0
IL_0002:  br.s       IL_0008

// loop starts here

// post iteration processing
IL_0004:  ldloc.0
IL_0005:  ldc.i4.1
IL_0006:  add
IL_0007:  stloc.0

// stop condition check
IL_0008:  ldloc.0
IL_0009:  ldc.i4.s   10
IL_000b:  bge.s      IL_003e

// initialization of temporary "tmp" variable for switch
IL_000d:  ldloc.0
IL_000e:  stloc.3

// check if "tmp" is 3
IL_000f:  ldloc.3
IL_0010:  ldc.i4.1
// if so go to "variable" intialization
IL_0011:  beq.s      IL_0019

// check if "tmp" is 5
IL_0013:  ldloc.3
IL_0014:  ldc.i4.5
IL_0015:  beq.s      IL_0023

// go to display
IL_0017:  br.s       IL_002b

// initialization of "variable"
IL_0019:  ldstr      "One"
IL_001e:  stloc.2
...

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

15.12.2012
Новые материалы

Структуры данных в C ++ - Часть 1
Реализация общих структур данных в C ++ C ++ - это расширение языка программирования C, которое поддерживает создание классов, поэтому оно известно как C с классами . Он используется для..

Как я опубликовал свое первое приложение в App Store в 13 лет
Как все началось Все началось три года назад летом после моего четвертого класса в начальной школе. Для меня, четвертого класса, лето кажется бесконечным, пока оно не закончится, и мой отец..

Что в лицо
Очерк о возвращении физиогномики и о том, почему мы должны это приветствовать. История начинается со странной науки. Р. Тора Бьорнсдоттир, Николас О. Рул. Видимость социального класса по..

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

Создание дизайна обуви с помощью машинного обучения
Обувь. Что подождать? Я думал, что речь пойдет о машинном обучении! Ну это так. Если бы вы пошли на Amazon, сколько обуви вы бы нашли? Наверное, много, не так ли? Но много ли в них..

GraalVM в 2022 году: итоги года
2022 год был очень продуктивным для проекта и сообщества GraalVM. Вместе мы разработали множество новых функций, выпустили GraalVM для последних версий Java и новых платформ и увидели несколько..

Быстрая разработка: волшебный мир больших языковых моделей
РУКОВОДСТВО Быстрая разработка: волшебный мир больших языковых моделей Подход, основанный на данных, для получения наилучшего ответа Искусство и наука Можно ли совместить машинное..