Javaと.NETで文字列を変更できないのはなぜですか?


190

彼らStringがJavaと.NET(および他のいくつかの言語)で不変にすることを決めたのはなぜですか?彼らはなぜそれを変更可能にしなかったのですか?


13
私も同じ考えをしていましたが、元のポスターの場所を確認したところ、それらはベルギーからのものであることがわかりました。これは、彼らが英語を母国語とする可能性が低いことを意味します。ほとんどのネイティブが言語を大まかに把握しているという事実と相まって、私は彼女にいくらか余裕を持たせることにしました。
ベルガボブ2009年

8
ベルガボブ、ありがとうございます。私は彼女ではありません。私は彼です。どうやら人々は文化の違いを考慮していません。
chrissie1 2009年

7
私の謝罪-chrissieは(一般的に)英国の女の子の名前です-私を別の文化の違いの犠牲にしています:-)
belugabob 2009

ただ注意してくださいString。.NET では実際には内部で変更可能です。StringBuilder.NETの2.0変異する文字列。ここでそのままにしておきます。
Alvin Wong

実際、.NET文字列変更可能です。そして、それはハックの真実でもありません。
Bitterblue 14

回答:


204

よる効果的なJavaの、第4章、73ページ、第2版:

「これには多くの理由があります。不変クラスは、可変クラスよりも設計、実装、使用が簡単です。エラーが発生しにくく、安全です。

[...]

" 不変オブジェクトは単純です。不変オブジェクトは、1つの状態、つまり作成された状態にすることができます。すべてのコンストラクターがクラス不変式を確立していることを確認すると、これらの不変式が常にtrueのままになることが保証されます。あなたの側の努力はありません。

[...]

不変オブジェクトは本質的にスレッドセーフです。同期は必要ありません。それらは、同時にアクセスする複数のスレッドによって破損することはありません。これは、スレッドセーフを実現するための最も簡単な方法です。実際、不変オブジェクトに対する別のスレッドの影響を監視できるスレッドはありません。したがって、 不変オブジェクトは自由に共有できます

[...]

同じ章の他の小さな点:

不変オブジェクトを共有できるだけでなく、その内部を共有することもできます。

[...]

不変オブジェクトは、変更可能か不変かにかかわらず、他のオブジェクトの優れた構成要素になります。

[...]

不変クラスの唯一の本当の欠点は、個別の値ごとに個別のオブジェクトが必要なことです。


22
私の回答の2番目の文を読んでください。不変クラスは、可変クラスよりも設計、実装、使用が簡単です。エラーが発生しにくく、より安全です。
PRINCESS FLUFF、2011

5
@PRINCESSFLUFF可変スレッドの共有はシングルスレッドでも危険だと付け加えます。たとえば、レポートをコピーしますreport2.Text = report1.Text;。次に、別の場所でテキストを変更しますreport2.Text.Replace(someWord, someOtherWord);。これにより、最初のレポートだけでなく2番目のレポートも変更されます。
phoog 2012

10
@Samは「なぜ変更できないのか」と尋ねず、「なぜ変更不可にすることを決めたのか」と尋ねました。
ジェームズ

1
@PRINCESSFLUFFこの回答では、文字列を具体的に取り上げていません。それがOPの質問でした。それはとてもイライラします-これは常にSOで発生し、文字列の不変性の質問でも発生します。ここでの答えは、不変性の一般的な利点について述べています。では、なぜすべての型が不変ではないのでしょうか。戻って文字列をアドレス指定できますか?
Howiecamp 2017

@Howiecamp私は、文字列が変更可能であった可能性があるという答えによって暗黙的だと思います(仮想の変更可能な文字列クラスが存在することを妨げるものは何もありません)。彼らは単純化のためにそのようにそれをしないことに決めました、そしてそれは使用の99%のケースをカバーしたからです。他の1%のケースでは、依然としてStringBuilderを提供しています。
ダニエルガルシアルビオ

102

少なくとも2つの理由があります。

最初-セキュリティ http://www.javafaq.nu/java-article1060.html

Stringを不変にした主な理由はセキュリティでした。次の例を見てください。ログインチェック付きのファイルオープンメソッドがあります。このメソッドに文字列を渡して、呼び出しがOSに渡される前に必要な認証を処理します。Stringが変更可能だった場合、認証チェックの後、OSがプログラムからリクエストを取得する前に、なんらかの方法でコンテンツを変更することが可能でした。その後、任意のファイルをリクエストすることが可能です。したがって、ユーザーディレクトリでテキストファイルを開く権利があるが、なんとかしてファイル名を変更できたときにその場で「passwd」ファイルまたはその他のファイルを開くように要求できます。その後、ファイルを変更し、OSに直接ログインすることができます。

2番目-メモリ効率 http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html

JVMは「文字列プール」を内部的に維持します。メモリ効率を上げるために、JVMはプールからStringオブジェクトを参照します。新しいStringオブジェクトは作成されません。したがって、新しい文字列リテラルを作成すると、JVMはプールがすでに存在するかどうかをプールにチェックインします。すでにプールに存在する場合は、同じオブジェクトへの参照を与えるか、プールに新しいオブジェクトを作成します。同じStringオブジェクトへの参照が多数あります。誰かが値を変更すると、すべての参照に影響します。それで、太陽はそれを不変にすることに決めました。


これは再利用に関する優れた点であり、特にString.intern()を使用する場合は当てはまります。すべての文字列を不変にすることなく再利用することは可能でしたが、その時点で人生は複雑になる傾向があります。
jsight 2008

3
これらのどちらも、この日と年齢で私にとってひどく正当な理由ではないようです。
Brian Knoblauch、

1
私はメモリ効率の引数にあまり確信していません(つまり、2つ以上のStringオブジェクトが同じデータを共有し、1つが変更されると、両方が変更されます)。MFCのCStringオブジェクトは、参照カウントを使用してそれを回避します。
RobH 2009年

7
セキュリティは、実際には不変文字列の存在理由の一部ではありません。OSは文字列をカーネルモードバッファーにコピーし、そこでアクセスチェックを行って、タイミング攻撃を回避します。それは本当にスレッドの安全性とパフォーマンスに関するものです:)
snemarch 2009年

1
メモリ効率の引数も機能しません。Cのようなネイティブ言語では、文字列定数は初期化されたデータセクション内のデータへの単なるポインタです-とにかく読み取り専用/不変です。「誰かが値を変更した場合」-ここでも、プールの文字列は読み取り専用です。
wj32 2009年

57

実際、Javaで文字列が不変である理由は、セキュリティとはあまり関係がありません。2つの主な理由は次のとおりです。

Thead Safety:

文字列は、非常に広く使用されているオブジェクトのタイプです。したがって、マルチスレッド環境での使用がほぼ保証されています。文字列は不変であり、スレッド間で文字列を共有しても安全です。不変文字列を使用すると、スレッドAから別のスレッドBに文字列を渡すときに、スレッドBがスレッドAの文字列を予期せず変更することがなくなります。

これは、マルチスレッドプログラミングのすでにかなり複雑なタスクを簡素化するだけでなく、マルチスレッドアプリケーションのパフォーマンスにも役立ちます。可変オブジェクトへのアクセスは、複数のスレッドからアクセスできる場合、何らかの方法で同期させて、別のスレッドによって変更されている間、あるスレッドがオブジェクトの値を読み取らないようにする必要があります。適切な同期は、プログラマーにとって正しく行うのが難しく、実行時に費用がかかります。不変オブジェクトは変更できないため、同期する必要はありません。

パフォーマンス:

文字列のインターニングについて言及しましたが、Javaプログラムのメモリ効率の向上はわずかです。文字列リテラルのみがインターンされます。つまり、ソースコードで同じ文字列のみが同じ文字列オブジェクトを共有します。プログラムが動的に同じ文字列を作成する場合、それらは異なるオブジェクトで表されます。

さらに重要なことに、不変の文字列を使用すると、内部データを共有できます。多くの文字列操作の場合、これは基本となる文字の配列をコピーする必要がないことを意味します。たとえば、Stringの最初の5文字を​​取得するとします。Javaでは、myString.substring(0,5)を呼び出します。この場合、substring()メソッドは、myStringの基礎となるchar []を共有する新しいStringオブジェクトを作成するだけで、インデックス0で始まり、そのchar []のインデックス5で終わることを知っています。これをグラフィック形式にするには、次のようになります。

 |               myString                  |
 v                                         v
"The quick brown fox jumps over the lazy dog"   <-- shared char[]
 ^   ^
 |   |  myString.substring(0,5)

これにより、この種の操作は非常に安価になり、O(1)は元の文字列の長さにも、抽出する必要のある部分文字列の長さにも依存しないためです。多くの文字列は基礎となるchar []を共有できるため、この動作にはメモリの利点もあります。


6
基礎を共有する参照として部分文字列を実装することchar[]は、かなり疑問の多い設計上の決定です。ファイル全体を単一の文字列に読み込み、1文字の部分文字列への参照を維持する場合、ファイル全体をメモリに保持する必要があります。
Gabe

5
正確には、ページ全体からいくつかの単語を抽出するだけで十分なウェブサイトクローラーを作成しているときに、特定の問題に遭遇しました。ページ全体のHTMLコードはメモリ内にあり、char []を共有する部分文字列のため、数バイトしか必要としませんが、HTMLコード全体を保持しました。そのための回避策は、新しいString(original.substring(..、..))を使用することです。String(String)コンストラクターは、基になる配列の関連する範囲のコピーを作成します。
LordOfThePigs 2010

1
以降の変更をカバーする補遺:Jave 7以降、String.substring()上記のコメントで言及されている問題を回避するために、完全なコピーを実行します。Java 8では、char[]共有を可能にする2つのフィールド、つまりcountおよびoffsetが削除され、Stringインスタンスのメモリフットプリントが削減されました。
Christian Semrau 2013年

Thead Safetyの部分に同意しますが、部分文字列の場合は疑います。
Gqqnbig 2014年

@LoveRight:次に、java.lang.Stringのソースコード(grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…)を確認します。これは、Java 6(これはこの回答が書かれたときは最新でした)。私は明らかにJava 7で変更しました
LordOfThePigs '29年

28

スレッドの安全性とパフォーマンス。文字列を変更できない場合、複数のスレッド間で参照を渡すことは安全かつ迅速です。文字列が変更可能な場合は、常に文字列のすべてのバイトを新しいインスタンスにコピーするか、同期を提供する必要があります。一般的なアプリケーションは、文字列を変更する必要があるたびに、文字列を100回読み取ります。不変性に関するウィキペディアを参照してください。


11

「なぜXが変更可能である必要があるのですか?」すでにプリンセスフラッフによって言及されている利点があるため、デフォルトは不変性に設定することをお勧めします。何かが変更可能であることは例外であるべきです。

残念ながら、現在のプログラミング言語のほとんどはデフォルトで可変性になっていますが、将来的にはデフォルトが不変性により多くなることを期待しています(次の主流のプログラミング言語のウィッシュリストを参照)。


7

うわー!ここの誤報は信じられません。String不変であることは、セキュリティには何の意味もありません。実行中のアプリケーションのオブジェクトに誰かがすでにアクセスしている場合(誰かがStringアプリを「ハッキング」しないようにする場合は、これを想定する必要があります)、ハッキングに利用できる機会は他にもたくさんあります。

の不変性が Stringスレッドの問題に対処し。うーん...私は2つの異なるスレッドによって変更されているオブジェクトを持っています。どうすれば解決できますか?オブジェクトへのアクセスを同期しますか?Naawww ...誰もオブジェクトを変更させないようにしましょう-面倒な同時実行の問題はすべて修正されます!実際、すべてのオブジェクトを不変にして、Java言語から同期化された構造を削除することができます。

本当の理由(上記で他の人が指摘)はメモリの最適化です。どのアプリケーションでも、同じ文字列リテラルを繰り返し使用することはよくあります。実際、数十年前に多くのコンパイラーがStringリテラルの単一のインスタンスのみを格納するように最適化したのは、非常に一般的です。この最適化の欠点は、Stringリテラルを変更するランタイムコードが、それを共有する他のすべてのコードのインスタンスを変更するため、問題が発生することです。たとえば、アプリケーションのどこかにある関数がStringリテラル"dog"をに変更するのはよくありません"cat"。A printf("dog")はリテラルになります(つまり、不変にします)。一部のコンパイラー(OSからのサポート付き)は、"cat" stdoutに書き込まれます。そのため、変更を試みるコードから保護する方法が必要でしたStringString リテラルを特別な読み取り専用メモリセグメントに挿入すると、書き込みが行われた場合にメモリエラーが発生します。

Javaではこれはインターニングとして知られています。ここでのJavaコンパイラーは、何十年もの間コンパイラーによって行われた標準的なメモリー最適化に続いています。またString、実行時に変更されるこれらのリテラルの同じ問題に対処するために、JavaはStringクラスを不変にするだけです(つまり、Stringコンテンツを変更できるセッターを提供しません)。リテラルのString内部処理がString発生しなかった場合、sは不変である必要はありません。


3
不変性とスレッド化コメントについては私は強く反対します。あなたはそこにポイントをまったく理解していないようです。そして、Javaインプリメンターの1人であるJosh Blochがそれが設計上の問題の1つであると言った場合、それはどのようにして誤った情報になるのでしょうか?
javashlook 2009年

1
同期は高価です。変更可能なオブジェクトへの参照は、不変ではなく同期する必要があります。それが可変である必要がない限り、すべてのオブジェクトを不変にする理由です。文字列は不変である可能性があるため、複数のスレッドでより効率的になります。
David Thornley

5
@ジム:メモリの最適化は「THE」の理由ではなく、「A」の理由です。Davidが述べたように、不変オブジェクトは本質的にスレッドセーフであり、高価な同期を必要としないため、スレッドセーフもAの理由です。スレッドセーフは、実際にはオブジェクトが不変であることの副作用です。同期は、オブジェクトを「一時的に」不変にする方法と考えることができます(ReaderWriterLockによってオブジェクトが読み取り専用になり、通常のロックによって完全にアクセスできなくなります。もちろん、オブジェクトも不変になります)。
Triynko、2009年

1
@DavidThornley:可変値ホルダーへの複数の独立した参照パスを作成すると、実質的にエンティティに変換され、スレッド化の問題を別として考えるのがはるかに困難になります。一般に、可変オブジェクトは、それぞれに1つの参照パスが存在する場合は不変オブジェクトよりも効率的ですが、不変オブジェクトを使用すると、参照を共有することでオブジェクトのコンテンツを効率的に共有できます。最良のパターンはとで例示されStringStringBufferいますが、残念ながら他のタイプはそのモデルに従いません。
スーパーキャット2014

7

String はプリミティブ型ではありませんが、通常は値のセマンティクスで、つまり値のように使用します。

価値とは、信頼しても変わらないものです。あなたが書いた場合:String str = someExpr(); あなたがで何かをしない限り、あなたはそれを変えたくないでしょうstr

StringObjectは当然ポインタのセマンティクスを持っているので、値のセマンティクスも取得するには不変である必要があります。


7

1つの要因は、Stringsが変更可能な場合、sを格納するオブジェクトはString、内部データが予告なく変更されないように、コピーを格納するように注意する必要があるということです。Stringsは数値のようにかなりプリミティブな型であることを考えると、たとえ参照によって渡されたとしても、値によって渡されたかのように扱うことができると便利です(これもメモリの節約に役立ちます)。


6

これがバンプであることは知っていますが、...本当に不変ですか 以下を検討してください。

public static unsafe void MutableReplaceIndex(string s, char c, int i)
{
    fixed (char* ptr = s)
    {
        *((char*)(ptr + i)) = c;
    }
}

...

string s = "abc";
MutableReplaceIndex(s, '1', 0);
MutableReplaceIndex(s, '2', 1);
MutableReplaceIndex(s, '3', 2);
Console.WriteLine(s); // Prints 1 2 3

拡張メソッドにすることもできます。

public static class Extensions
{
    public static unsafe void MutableReplaceIndex(this string s, char c, int i)
    {
        fixed (char* ptr = s)
        {
            *((char*)(ptr + i)) = c;
        }
    }
}

これは次の作業を行います

s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);

結論:コンパイラーが認識している不変の状態です。ただし、Javaにはポインタがないため、上記は.NET文字列にのみ適用されます。ただし、C#のポインターを使用して、文字列を完全に変更可能にすることができます。これは、ポインタの使用方法、実用的な使用方法、または安全に使用される方法ではありません。しかし、それは可能であり、したがって「可変」ルール全体を曲げます。通常、文字列のインデックスを直接変更することはできません。これが唯一の方法です。これは、文字列のポインターインスタンスを禁止するか、文字列がポイントされたときにコピーを作成することで防ぐことができますが、どちらも行われず、C#の文字列が完全に不変ではありません。


1
+1。.NET文字列は実際には不変ではありません。実際、これはパフォーマンス上の理由から、常にStringクラスとStringBuilderクラスで行われます。
James Ko

3

ほとんどの目的で、「文字列」は、数値のように、意味のある原子単位 (使用/処理/思考/仮定)ですです。

したがって、文字列の個々の文字が変更できない理由を尋ねるのは、整数の個々のビットが変更できない理由を尋ねるのと同じです。

理由を知っておくべきです。考えてみてください。

私はそれを言うのが嫌いですが、残念ながら、私たちの言語はひどいので、私たちはこれについて議論しています、そして私たちは単一の単語、文字列を使用しようとしています、状況に応じた複雑な概念またはオブジェクトのクラスを記述ます。

数値の場合と同様に、「文字列」を使用して計算と比較を行います。文字列(または整数)が可変である場合、あらゆる種類の計算を確実に実行するために、それらの値を不変のローカルフォームにロックする特別なコードを記述する必要があります。したがって、文字列は数値識別子のように考えるのが最善ですが、16、32、または64ビット長ではなく、数百ビットの長さになる可能性があります。

誰かが「ひも」と言うとき、私たちはみんな違うことを考えます。特に目的を問わず、単なるキャラクターの集まりだと思っている人は、当然のことながら、キャラクターを操作できないように決めだけでびっくりします。しかし、 "string"クラスは単なる文字の配列ではありません。それはSTRINGではなくchar[]です。「文字列」と呼ぶ概念にはいくつかの基本的な前提があり、一般に、数値などのコード化されたデータの意味のある原子単位として説明できます。人々は「文字列操作」について話すとき、おそらく彼らは本当に操作の話をしている文字をビルドする文字列、およびStringBuilderのは、そのための素晴らしいです。

文字列が変更可能であるとしたら、それがどのようなものか少し考えてください。次のAPI関数は、変更可能なユーザー名の文字列が、この関数が使用している間に別のスレッドによって意図的または非意図的に変更された場合、別のユーザーの情報を返すようにだまされる可能性があります。

string GetPersonalInfo( string username, string password )
{
    string stored_password = DBQuery.GetPasswordFor( username );
    if (password == stored_password)
    {
        //another thread modifies the mutable 'username' string
        return DBQuery.GetPersonalInfoFor( username );
    }
}

セキュリティは、「アクセス制御」だけでなく、「安全性」と「正確性の保証」にも関係しています。メソッドを簡単に記述できず、単純な計算または比較を確実に実行するために依存できない場合、そのメソッドを呼び出すのは安全ではありませんが、プログラミング言語自体に問題を呼び出すのは安全です。


C#では、文字列はそのポインターによって(unsafeまたはを使用して)、または単にリフレクションによって変更できます(基になるフィールドを簡単に取得できます)。これは、意図的に文字列を変更したい人なら誰でも簡単に変更できるため、セキュリティ上のポイントを無効にします。ただし、これはプログラマにセキュリティを提供します。特別なことをしない限り、文字列は不変であることが保証されます(ただし、スレッドセーフではありません)。
Abel、

はい、ポインタを介して任意のデータオブジェクト(string、intなど)のバイトを変更できます。ただし、文字を変更するためのパブリックメソッドが組み込まれていないという意味で、文字列クラスが不変である理由について説明しています。文字列は数字によく似ていると言っていましたが、個々の文字を操作することは、数字の個々のビットを操作すること(文字列を(バイト配列としてではなく)トークンとして扱う場合)と同じであり、数字は数値(ビットフィールドとしてではなく)サブオブジェクトレベルではなく、概念的なオブジェクトレベルで話している
Triynko

2
また、明確にするために、オブジェクト指向コードのポインターは本質的に安全ではありません。それは、クラスに定義されたパブリックインターフェイスを回避するためです。私が言っていたのは、文字列のパブリックインターフェイスが他のスレッドによる変更を許可した場合、関数は簡単にだまされる可能性があるということでした。もちろん、ポインタを使用してデータに直接アクセスすることで常にだまされる可能性がありますが、それほど簡単ではなく、意図せずに行われることはありません。
Triynko、2009年

1
「オブジェクト指向コードのポインタは、参照と呼ばない限り、本質的に安全ではありません」。Javaでの参照は、C ++でのポインターと同じです(ポインター演算のみが無効です)。別のコンセプトは、管理可能または手動のメモリ管理ですが、それは異なります。あなたは、GCが(反対は、到達可能性のセマンティクスが難しくきれい、しかしunfeasableない作ることであろうという意味では難しいだろう)せずに参照セマンティクス(なし算術ポインタ)を持っている可能性が
dribeas -デビッド・ロドリゲス

もう1つは、文字列がほぼ不変であるが、完全ではない場合(ここでは十分なCLIを知らない)、セキュリティ上の理由から非常に悪い可能性があることです。一部の古いJava実装ではそれを実行でき、文字列を内部化するためにそれを使用するコードのスニペットを見つけ(同じ値を持つ他の内部文字列を見つけ、ポインターを共有し、古いメモリブロックを削除して)、バックドアを使用しました別のクラスで不正な動作を強制する文字列の内容を書き換えます。( "DELETE"に"SELECT *"書き換え考えてみましょう)
デビッド・ロドリゲス- dribeas

3

不変性はセキュリティとそれほど密接に結びついていません。そのため、少なくとも.NETでは、SecureStringクラスを取得します。

後の編集:Java GuardedStringでは、同様の実装が見つかります。


2

C ++で文字列を変更可能にするという決定は多くの問題を引き起こします。KelvinHenneyによるMad COW Diseaseに関するこの優れた記事を参照してください。

COW =書き込み時のコピー。


2

それはトレードオフです。Strings Stringプールに移動し、同じものを複数作成するとStringのsを、それらは同じメモリを共有します。プログラムは同じ文字列を何度もグラインドする傾向があるため、デザイナーはこのメモリ節約手法が一般的なケースでうまく機能すると考えました。

欠点は、連結によって多くの余分なが作成され、それが一時的なものにStringなり、ガベージになり、実際にメモリのパフォーマンスが低下することです。あなたは持っているStringBufferStringBuilder(Javaで、StringBuilderこれらのケースでメモリを維持するために使用する.NETでもあります)。


1
「inter()」の文字列を明示的に使用しない限り、「文字列プール」はすべての文字列に自動的に使用されるわけではないことに注意してください。
jsight 2008

2

StringJavaのsは真に不変ではありません。リフレクションやクラスローディングを使用して、それらの値を変更できます。セキュリティのためにそのプロパティに依存するべきではありません。例については、JavaのMagic Trickを参照してください。


1
私は、コードが完全な信頼で実行されている場合にのみそのようなトリックを実行できると信じています。したがって、セキュリティ上の損失はありません。JNIを使​​用して、文字列が格納されているメモリの場所に直接書き込むこともできます。
Antoine Aubry

実際には、リフレクションによって不変オブジェクトを変更できると思います。
Gqqnbig 2014年

0

不変性は良好です。効果的なJavaを参照してください。文字列を渡すたびに文字列をコピーしなければならない場合は、エラーが発生しやすいコードになります。また、どの変更がどの参照に影響するかについても混乱しています。Integerがintのように動作するために不変でなければならないのと同じように、Stringはプリミティブのように動作するために不変として動作する必要があります。C ++では、文字列を値で渡すと、ソースコードで明示的に言及することなくこれが行われます。


0

ほとんどすべてのルールに例外があります。

using System;
using System.Runtime.InteropServices;

namespace Guess
{
    class Program
    {
        static void Main(string[] args)
        {
            const string str = "ABC";

            Console.WriteLine(str);
            Console.WriteLine(str.GetHashCode());

            var handle = GCHandle.Alloc(str, GCHandleType.Pinned);

            try
            {
                Marshal.WriteInt16(handle.AddrOfPinnedObject(), 4, 'Z');

                Console.WriteLine(str);
                Console.WriteLine(str.GetHashCode());
            }
            finally
            {
                handle.Free();
            }
        }
    }
}

-1

これは主にセキュリティ上の理由によるものです。Stringが改ざん防止されていると信頼できない場合は、システムを保護するのがはるかに難しくなります。


1
「改ざん防止」の意味する例を挙げてください。この答えは、本当に文脈から外れているように感じます。
Gergely Orosz
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.