狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

幾張圖帶你了解.NET String

freeflydom
2024年10月28日 11:54 本文熱度 585

String

字符串作為一種特殊的引用類型,是迄今為止.NET程序中使用最多的類型??梢哉f是萬物皆可string

因此在分析dump的時候,大量字符串對象是很常見的現(xiàn)象

string的不可變性

string作為引用類型,那就意味是可以變化的.但在.NET中,它們默認不可變。
也就是說行為類似值類型,實際上是引用類型的特殊情況。

但是,"字符串具有不可變性"僅在.NET平臺下成立,只是因為在BCL(Basic Class Library)中并未提供改變string內(nèi)容的方法而已。
在C/C++/F# 中,是可以改變的。因此,我們完全可以在底層實現(xiàn)修改字符串內(nèi)容

眼見為實

示例1

示例代碼


可以看到,string的值為aaa


通過算法:address + 0x10 + 2 * sizeof(char) ,我們直接修改內(nèi)存的內(nèi)容

可以看到,同一個內(nèi)存地址,里面的值已經(jīng)從"aaa"變成了"aab".

示例2

點擊查看代碼

字符串的可變行為

那么在日常使用中,我們需要大量字符串拼接的時候。如何改進呢?
最常見的辦法就是使用Stringbuilder.

Stringbuilder源碼解析

 public sealed partial class StringBuilder : ISerializable
 {
 		//存儲字符串的char[]
        internal char[] m_ChunkChars;
		//StringBuilder之間使用鏈表來關(guān)聯(lián)
        internal StringBuilder? m_ChunkPrevious;
		
        public StringBuilder(string? value, int startIndex, int length, int capacity)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(capacity);
            ArgumentOutOfRangeException.ThrowIfNegative(length);
            ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
            value ??= string.Empty;
            if (startIndex > value.Length - length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_IndexLength);
            }
            m_MaxCapacity = int.MaxValue;
            if (capacity == 0)
            {
                capacity = DefaultCapacity;
            }
            capacity = Math.Max(capacity, length);
            m_ChunkChars = GC.AllocateUninitializedArray<char>(capacity);
            m_ChunkLength = length;
            value.AsSpan(startIndex, length).CopyTo(m_ChunkChars);
        }
		public StringBuilder Append(char value, int repeatCount)
        {
            if (repeatCount == 0)
            {
                return this;
            }
            char[] chunkChars = m_ChunkChars;
            int chunkLength = m_ChunkLength;
    		// 嘗試在當前塊中放入所有重復(fù)字符
    		// 使用與 Span<T>.Slice 相同的檢查,以便在 64 位系統(tǒng)中進行折疊
    		// 因為 repeatCount 不能為負數(shù),所以在 32 位系統(tǒng)中不會溢出
            if (((nuint)(uint)chunkLength + (nuint)(uint)repeatCount) <= (nuint)(uint)chunkChars.Length)
            {
				//使用Span高性能填充char[]
                chunkChars.AsSpan(chunkLength, repeatCount).Fill(value);
                m_ChunkLength += repeatCount;
            }
            else
            {
				//如果空間不足,則進行擴容
                AppendWithExpansion(value, repeatCount);
            }
            return this;
        }
		public override string ToString()
        {
			// 分配一個新的字符串用于存儲結(jié)果
            string result = string.FastAllocateString(Length);
            StringBuilder? chunk = this;
            do
            {
                if (chunk.m_ChunkLength > 0)
                {
                   // 將這些值復(fù)制到局部變量中,以確保在多線程環(huán)境下的穩(wěn)定性
                    char[] sourceArray = chunk.m_ChunkChars;
                    int chunkOffset = chunk.m_ChunkOffset;
                    int chunkLength = chunk.m_ChunkLength;
					// 使用內(nèi)存移動復(fù)制數(shù)據(jù)到result中
                    Buffer.Memmove(
                        ref Unsafe.Add(ref result.GetRawStringData(), chunkOffset),
                        ref MemoryMarshal.GetArrayDataReference(sourceArray),
                        (nuint)chunkLength);
                }
				//移動到上一個StringBuilder中,鏈表式讀取
                chunk = chunk.m_ChunkPrevious;
            }
            while (chunk != null);
            return result;
        }
 }

在Stringbuilder的內(nèi)部,內(nèi)部使用char[] m_ChunkChars將文本保存。并且使用Span方式直接高性能操作內(nèi)存。

避免對象分配是改進代碼性能的最常見方法
string.format/string.join/$"name={name}" 等常見函數(shù)均已在內(nèi)部實現(xiàn)Stringbuilder

字符串為什么不可變?

那么既然string的反直覺,那么為什么要這么設(shè)計呢?原因有如下幾點

  1. 安全性
    string的使用范圍太廣了,比如new Dictionary<string, string>(),用戶token,文件路徑。它們的用途都代表一個key,如果這個key能被程序隨意修改。那么將毫無安全性可言。
  2. 并發(fā)性
    正因為string使用范圍大,所以很多場景都可能存在并發(fā)訪問,如果可變,那么需要承擔(dān)額外的同步開銷。

為什么string不是一個結(jié)構(gòu)?

上面說了這么多,結(jié)構(gòu)完美滿足了不可變/并發(fā)安全 這兩個條件,那為什么不把string定義為結(jié)構(gòu)?
其核心原因在于,結(jié)構(gòu)的傳值語義會導(dǎo)致頻繁復(fù)制字符串
而復(fù)制大字符串的開銷太大了,因此使用傳引用語義要高效得多

JSON 的序列化/反序列化就是一個典型的例子

字符串暫存

.NET Rumtime內(nèi)部有一個string interning 機制
當兩個字符串一模一樣的時候,不需要在內(nèi)存中存兩份。只保留一份即可

但字符串暫存有個限制,默認情況下是只暫存靜態(tài)創(chuàng)建的字符串的。也就是靜態(tài)值才會被暫存起來.由JIT來判斷是否暫存

舉個例子

究其原因是因為這樣做開銷巨大,創(chuàng)建一個新字符串時,runtime需要動態(tài)的檢測它是否已被暫存。如果被檢測的字符串相當龐大或數(shù)量特別多,那么花銷同樣也很大

FCL提供了顯式API string.IsInterned/string.Intern 來讓我們可以主動暫存字符串。

字符串被暫存在哪里?

https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/stringliteralmap.cpp

這時大家可以思考一下,暫存的字符串跟靜態(tài)變量有什么區(qū)別? 都是永遠不會被釋放的對象
因此可以猜到。字符串應(yīng)該是被暫存在AppDomain中。與高頻堆應(yīng)該相鄰在一起.

在.NET內(nèi)部Appdomain中,有一個私有堆叫String Literal Map的對象,內(nèi)部存儲著字符串的hash與一個內(nèi)存地址。
內(nèi)存地址指向另外一個數(shù)據(jù)結(jié)構(gòu)LargeHeapHandleTable .位于LOH堆中,LargeHeapHandleTable內(nèi)部包含了對字符串實例的引用

在正常情況下,只有>85000字節(jié)的才會被分配到LOH堆中,LargeHeapHandleTable就是一個典型的例外。一些不會被回收/很難被回收的對象即使沒有超過85000也會分配在LOH堆中。因為這樣可以減少GC的工作量(不會升代,不會壓縮)

眼見為實

挖坑待埋,sos并未提供String Literal Map的堆地址,待我摸索幾天

安全字符串

在使用string的過程中,可能包含敏感對象。比如Password.
String對象內(nèi)部使用char[]來承載。因此攜帶敏感信息的string。被執(zhí)行了unsafe或者非托管代碼的時候。就有可能被掃描內(nèi)存。
只有對象被GC回收后,才是安全的。但是中間的時間差足夠被掃描N次了。

為了解決此問題,在FCL中添加了SecureString類。作為上位替代

  1. 內(nèi)部使用UnmanagedBuffer來代替char[]
public sealed partial class SecureString : IDisposable
{
		private readonly object _methodLock = new object();//同步鎖
        private UnmanagedBuffer? _buffer; //使用UnmanagedBuffer代替char[]
		public SecureString()
        {
			_buffer = UnmanagedBuffer.Allocate(GetAlignedByteSize(value.Length));
            _decryptedLength = value.Length;
            SafeBuffer? bufferToRelease = null;
            try
            {
                Span<char> span = AcquireSpan(ref bufferToRelease);
                value.CopyTo(span);
            }
            finally
            {
                ProtectMemory();
                bufferToRelease?.DangerousRelease();
            }
        }
		
		public void AppendChar(char c)
        {
            lock (_methodLock)
            {
                EnsureNotDisposed();
                EnsureNotReadOnly();
                Debug.Assert(_buffer != null);
                SafeBuffer? bufferToRelease = null;
                try
                {
				    //解密內(nèi)存以便進行修改
                    UnprotectMemory();
                    EnsureCapacity(_decryptedLength + 1);
                    Span<char> span = AcquireSpan(ref bufferToRelease);
                    span[_decryptedLength] = c;
                    _decryptedLength++;
                }
                finally
                {
					//重新加密
                    ProtectMemory();
                    bufferToRelease?.DangerousRelease();
                }
            }
        }
}
  1. 實現(xiàn)了IDisposable接口,開發(fā)可以手動執(zhí)行Dispose().對內(nèi)存緩沖區(qū)直接清零,確保惡意代碼無法獲得敏感信息

        public void Dispose()
        {
            lock (_methodLock)
            {
                if (_buffer != null)
                {
                    _buffer.Dispose();
                    _buffer = null;
                }
            }
        }

安全字符串真的安全嗎?

SecureString的目的是避免在進程中使用純文本存儲機密信息
SecureString的底層本質(zhì)上也是一段未加密的char[],由FCL進行數(shù)據(jù)加密/解密。
因此只有.NET Framework 中,內(nèi)部的char[]由windows提供支持,是加密的
但在.NET Core中,其他平臺并未提供系統(tǒng)層面的支持

https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md

因此,個人認為真正的"銀彈". 是數(shù)據(jù)本身就是加密的。比如從數(shù)據(jù)庫中存儲就是加密內(nèi)容,或者配置文件中本身就是加密的。因為操作系統(tǒng)沒有安全字符串的概念。

惡意代碼只要能讀內(nèi)存,且內(nèi)存本身未加密。那么在CLR層上就是裸奔

轉(zhuǎn)自https://www.cnblogs.com/lmy5215006/p/18494483


該文章在 2024/10/28 11:54:36 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved