Поврежденная строка в С#

Я наткнулся на "CorruptedString" (решение). Ниже приведен код программы из книги:

var s = "Hello";
string.Intern(s);
unsafe
{
  fixed (char* c = s)
    for (int i = 0; i < s.Length; i++)
      c[i] = 'a';
}
Console.WriteLine("Hello"); // Displays: "aaaaa"

Почему в этой программе отображается "aaaaa"? Я понимаю эту программу следующим образом:

  • CLR резервирует "привет" в стаже пула (я изображаю пул-пул как набор строк).
  • string.Intern(s) фактически ничего не делает, потому что CLR зарезервировал строку "Hello" - он просто возвращает адрес зарезервированной строки "Hello" (объект s имеет тот же адрес)
  • Программа изменяет содержимое строки "Hello" с помощью указателя
  • ??? Строка Hello должна отсутствовать в первом пуле, и это должно быть ошибкой! Но все в порядке; программа выполняется успешно.

Как я понимаю, пул-пул, это как какой-то словарь строки для строки. Или, может быть, я что-то пропустил?

Ответ 1

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

for (int i = 0; i < s.Length; i++)
      c[i] = 'a';

вы редактируете что в памяти. Когда он в следующий раз обратится к хранилищу интернированных строк, он будет использовать один и тот же адрес в памяти, удерживая данные, которые вы только что изменили. Это было бы невозможно без unsafe. string.Intern(s); здесь не играет роли; он ведет себя так же, если вы прокомментируете это.

Тогда по

Console.WriteLine("Hello"); // Displays: "aaaaa"

.NET смотрит, есть ли запись для адреса, полученного для "Hello", и есть: тот, который вы только что обновили, чтобы быть "aaaaa". Число символов 'a' определяется длиной "Hello".

Ответ 2

Несмотря на то, что @Jaroslav Kadlec ответ правильный и полный, я хотел бы добавить дополнительную информацию о поведении кода и почему string.Intern(s); бесполезен в этом случае.

О международном пуле

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

Однако важно заметить , что только явная объявленная строка интерполируется на этапе компиляции.

Рассмотрим следующий код:

var first = "Hello"; //Will be interned
var second = "World"; //Will be interned
var third = first + second; //Will not be interned

Конечно, в некоторых случаях мы хотели бы запустить некоторую строку во время выполнения, и это можно сделать с помощью String.Intern после проверки с помощью String.IsInterned.

Итак, вернемся к фрагменту OP:

//...
var s = "Hello";
string.Intern(s);
//...

В этом случае string.Intern(s); бесполезен, поскольку он уже интернирован на этапе компиляции.