Skip to content

Instantly share code, notes, and snippets.

@ufcpp
Last active August 6, 2019 02:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ufcpp/070d4fd17ff9d2c3b8749d1249b87fd2 to your computer and use it in GitHub Desktop.
Save ufcpp/070d4fd17ff9d2c3b8749d1249b87fd2 to your computer and use it in GitHub Desktop.
string を書き換え可能な一時バッファーとして使う
using System;
public class Program
{
static void Main()
{
const int BufferLength = 256;
// string を書き換え可能な一時バッファーとして使う。
// 普通は new char[] とか stackalloc char[] を使えばこんな邪悪な真似しなくていいんだけど、string しか受け付けない API を何ループも呼び出す必要がありそうで…
// char* とか Span<char> を受け付けるオーバーロードさえ持っていてくれれば…
var str = new string('\0', BufferLength);
MutateString(str, 'a', 5);
show(str);
MutateString(str, 'b', 10);
show(str);
MutateString(str, 'c', 15);
show(str);
void show(string str) => Console.WriteLine($"({str.Length}) {str}");
}
// 本来は書き換え不能な string を書き換えて、元よりも短い文字列を作っちゃう。
// new した string に対してはこれをやってもたぶん平気だけど、
// リテラルから ldstr した文字列に対してやるとだいぶ危なそう。
// 実際、stackoverflow で「プロセスが落ちることがある」みたいな質問見かけた。
//
// クラッシュしないとしても、Dictionary のキーに使ったりするとだいぶまずい。
// 同じインスタンスの hash code が変わっちゃうんで。
static unsafe void MutateString(string target, char c, int length)
{
fixed (char* p = target)
{
var pi = (int*)p;
// fixed で取ったポインターの4 (sizeof(int))バイト前に長さが入ってる。
// .NET の string は BSTR 互換だからこの仕様が変わることはないと思うものの…
// undocumented な挙動のはず。
*(pi - 1) = length;
for (int i = 0; i < length; i++)
{
p[i] = c;
}
p[length] = '\0';
}
}
}
using System;
public class Program
{
static void Main()
{
const int N = 100;
const int BufferLength = 256;
for (int i = 0; i < N; i++)
{
// string を書き換え可能な一時バッファーとして使う。
// 普通は new char[] とか stackalloc char[] を使えばこんな邪悪な真似しなくていいんだけど、string しか受け付けない API を何ループも呼び出す必要がありそうで…
// char* とか Span<char> を受け付けるオーバーロードさえ持っていてくれれば…
var str = new string('\0', BufferLength);
for (int j = 0; j < N; j++)
{
MutateString(str, 'a', 50);
show(str);
for (int k = 0; k < N; k++)
{
_ = new int[10000];
}
MutateString(str, 'b', 100);
show(str);
for (int k = 0; k < N; k++)
{
_ = new int[10000];
}
// ここで AccessViolation 起こす。
// GC は Length 見てコンパクションしてそう。
MutateString(str, 'c', 150);
show(str);
}
}
void show(string str) => Console.WriteLine($"({str.Length}) {str}");
}
// 本来は書き換え不能な string を書き換えて、元よりも短い文字列を作っちゃう。
// new した string に対してはこれをやってもたぶん平気だけど、
// リテラルから ldstr した文字列に対してやるとだいぶ危なそう。
// 実際、stackoverflow で「プロセスが落ちることがある」みたいな質問見かけた。
//
// クラッシュしないとしても、Dictionary のキーに使ったりするとだいぶまずい。
// 同じインスタンスの hash code が変わっちゃうんで。
static unsafe void MutateString(string target, char c, int length)
{
fixed (char* p = target)
{
var pi = (int*)p;
// fixed で取ったポインターの4 (sizeof(int))バイト前に長さが入ってる。
// .NET の string は BSTR 互換だからこの仕様が変わることはないと思うものの…
// undocumented な挙動のはず。
*(pi - 1) = length;
for (int i = 0; i < length; i++)
{
p[i] = c;
}
p[length] = '\0';
}
}
}
@ufcpp
Copy link
Author

ufcpp commented Aug 6, 2019

ちなみに「BSTR 互換」のせいで、length 部分を4バイトから変えれないらしい。
64bit マシンでも32bit長を超える長さの文字列は確保できない。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment