(なぜ)初期化されていない変数の未定義の動作を使用していますか?


82

私が持っている場合:

この式の後でゼロになるx はずであること明らかです、私が見るところはどこでも、このコードの動作x(減算の前まで)の値だけでなく、未定義であると言われています

2つの質問:

  • このコードの動作は本当に未定義ですか?
    (たとえば、準拠システムでコードがクラッシュする可能性がありますか?)

  • もしそうなら、ここでゼロであるべきであることが完全に明らかであるのに、なぜCは動作が未定義でxあると言うのですか?

    つまり、ここで動作を定義しないことによってられる利点は何ですか?

明らかに、コンパイラは、単に使用できるものは何でもそれは、変数内の「便利」とみなし、それが意図したとおりに動作します...そのアプローチと間違って何ガベージ値はありますか?



3
ここで動作の特別なケースを定義することによって得られる利点は何ですか?確かに、@ Mehrdadは特定のまれなケースで変数を初期化することを避けたいので、すべてのプログラムとライブラリを大きくしたり遅くしたりできます。
ポールトゥームリン2012

9
@ W'rkncacnter私はそれがだまされていることに同意しません。取る値に関係なく、OPはx -= x。の後にゼロになることを期待します。問題は初期化されていない値へのアクセスがUBである理由です。
神秘的な2012

6
ステートメントx = 0というのは興味深いことです。通常、アセンブリでxor x、xに変換されます。ここでやろうとしていることとほとんど同じですが、減算の代わりにxorを使用します。
0xFE 2012

1
'つまり、ここで動作を定義しないことによって得られる利点は何ですか?'-1つ以上の変数に依存しない値を持つ式の無限大をリストしないという標準の利点は明らかだと思いました。同時に、@ Paul、このような標準の変更によって、プログラムやライブラリが大きくなることはありません。
ジムバルター2012

回答:


90

はい、この動作は定義されていませんが、ほとんどの人が認識しているのとは異なる理由があります。

まず、単一化された値を使用すること自体は未定義の動作ではありませんが、値は単に不確定です。値がタイプのトラップ表現である場合、これにアクセスするのはUBです。符号なし型にトラップ表現が含まれることはめったにないため、その側では比較的安全です。

動作を未定義にするのは、変数の追加のプロパティです。つまりregister、アドレスが取得されないということです。このような変数は、「初期化されていない」一種の余分な状態を持ち、タイプドメインの値に対応しない実際のCPUレジスタを持つアーキテクチャがあるため、特別に扱われます。

編集:標準の関連フレーズは6.3.2.1p2です:

左辺値が、レジスタストレージクラスで宣言された可能性のある自動ストレージ期間のオブジェクトを指定し(アドレスが取得されなかった)、そのオブジェクトが初期化されていない(イニシャライザで宣言されておらず、使用前に割り当てが実行されていない)場合)、動作は未定義です。

また、明確にするために、次のコードすべての状況で合法です。

  • ここでのアドレスaとは、bその価値がちょうど不確定であるので、撮影しています。
  • 以来unsigned char不定値がちょうど指定されていないことをトラップ表現を持ったことがない、のいずれかの値がunsigned char発生する可能性があります。
  • 最後に値を保持するa 必要があります0

Edit2: aおよびb不特定の値があります:

3.19.3 この国際規格がどのインスタンスでもどの値を選択するかについての要件を課していない、関連するタイプの不特定の値の
有効な値


6
何かが足りないかもしれませんunsignedが、確かにトラップ表現を持っているように思えます。そのように言っている規格の部分を指摘できますか?§6.2.6.2/ 1に次のように記載されています。「unsignedchar以外のunsignedintegerタイプの場合、オブジェクト表現のビットは、値ビットとパディングビットの2つのグループに分けられます(後者のいずれかである必要はありません)。 ...これは値表現と呼ばれます。パディングビットの値は指定されていません。⁴⁴⁾ "とコメント:"⁴⁴⁾パディングビットの組み合わせによっては、トラップ表現が生成される場合があります "。
conio 2013

6
コメントの続き:「たとえば、1つのパディングビットがパリティビットの場合、パディングビットの組み合わせによっては、トラップ表現が生成される場合があります。ただし、有効な値に対する算術演算では、次のような例外的な条件の一部として以外はトラップ表現を生成できません。オーバーフロー。これは、符号なしタイプでは発生しません。」-有効な値を使用できるようになったら、これはすばらしいことですが、不確定な値、初期化される前のトラップ表現である可能性があります(パリティビットの設定が間違っているなど)。
conio 2013

4
@conio以外のすべてのタイプについては正しいですunsigned charが、この回答はを使用していunsigned charます。ただし、厳密に準拠したプログラムはsizeof(unsigned) * CHAR_BIT、に基づいてUINT_MAX、特定の実装がのトラップ表現を持つことはできない可能性があることを計算および決定できますunsigned。そのプログラムがその決定を行った後、それはこの答えがで行うことを正確に実行するために進むことができunsigned charます。

4
@JensGustedt:memcpy気を散らすものではありません。つまり、*&a = *&b;。に置き換えられた場合でも、例は当てはまりません。
R .. GitHub STOP HELPING ICE 2015

4
@R ..もうわかりません。C委員会のメーリングリストで進行中の議論があり、これはすべて大きな混乱、つまり意図された(または意図された)行動と実際に書かれたものとの間の大きなギャップであるように思われます。ただし、明確なことは、メモリへのアクセスunsigned charmemcpy役立つため、メモリへのアクセス*&があまり明確ではないということです。これが落ち着いたら報告します。
Jens Gustedt 2015

24

C標準は、コンパイラーに最適化を実行するための多くの自由度を与えます。初期化されていないメモリがランダムなビットパターンに設定され、すべての操作が書き込まれた順序で実行されるプログラムの単純なモデルを想定した場合、これらの最適化の結果は驚くべきものになる可能性があります。

注:次の例はx、アドレスが取得されていないためにのみ有効であり、「レジスターのような」ものです。タイプにxトラップ表現がある場合にも有効です。これは、符号なしタイプの場合はめったになく(少なくとも1ビットのストレージを「浪費」する必要があり、文書化する必要があります)、では不可能ですunsigned charx符号付きタイプの場合、実装では、-(2 n-1 -1)と2 n- 1-1の間の数値ではないビットパターンをトラップ表現として定義できます。JensGustedtの回答を参照してください。

レジスタはメモリよりも高速であるため、コンパイラはレジスタを変数に割り当てようとします。プログラムはプロセッサがレジスタを持っているよりも多くの変数を使用する可能性があるため、コンパイラはレジスタ割り当てを実行します。これにより、異なる時間に同じレジスタを使用する異なる変数が生成されます。プログラムフラグメントを検討してください

3行目が評価されるとき、xまだ初期化されていないため、(コンパイラーの理由で)3行目は、コンパイラーが理解するのに十分なほど賢くない他の条件のために発生しない、ある種のまぐれである必要があります。z4行目以降は使用されず、x5行目以前も使用されないため、両方の変数に同じレジスタを使用できます。したがって、この小さなプログラムは、レジスタに対する次の操作にコンパイルされます。

の最終値xはの最終値でr0あり、の最終値yはの最終値ですr1。これらの値はx = -3およびy = -4であり、x適切に初期化された場合のように5および4ではありません。

より複雑な例については、次のコードフラグメントを検討してください。

コンパイラがcondition副作用がないことを検出したとします。以来condition変更されることはありませんx、コンパイラはループを通る最初の実行はおそらくアクセスすることができないことを知っているx、それはまだ初期化されていないため。したがって、ループ本体の最初の実行はと同等x = some_value()であり、条件をテストする必要はありません。コンパイラは、あなたが書いたかのようにこのコードをコンパイルするかもしれません

これをコンパイラ内でモデル化する方法は、に依存する値は、初期化されていない限り、便利な値x持っていると見なすことです。初期化されていない変数が未定義の場合の動作は、変数が単に未指定の値を持つのではなく、コンパイラーが便利な値の間の特別な数学的関係を追跡する必要がないためです。したがって、コンパイラは上記のコードを次のように分析できます。x

  • 最初のループ反復中に、評価xされるまでに初期化されません-x
  • -x 動作は未定義であるため、その値は何でも便利です。
  • 最適化ルールが適用されるため、このコードはに簡略化できます。condition ? value : valuecondition; value

あなたの質問のコードに直面したとき、この同じコンパイラは、x = - x評価されたときにの値-xが何でも便利であると分析します。したがって、割り当てを最適化することができます。

上記のように動作するコンパイラーの例は探していませんが、これは優れたコンパイラーが実行しようとする一種の最適化です。私はそれに遭遇しても驚かないでしょう。これは、プログラムがクラッシュするコンパイラのもっともらしい例です。(ある種の高度なデバッグモードでプログラムをコンパイルする場合、それほど信じられないことではないかもしれません。)

この架空のコンパイラは、すべての変数を異なるメモリページにマップし、ページ属性を設定して、初期化されていない変数から読み取ると、デバッガを呼び出すプロセッサトラップが発生するようにします。変数への割り当ては、最初にそのメモリページが正常にマップされていることを確認します。このコンパイラは、高度な最適化を実行しようとはしません。デバッグモードであり、初期化されていない変数などのバグを簡単に見つけることを目的としています。ときにx = - x評価され、右側には、トラップを引き起こし、デバッガがアップし起動します。


+1いい説明ですが、標準はその状況に特別な注意を払っています。その話の続きについては、以下の私の答えを参照してください。(コメントとして持つには長すぎます)。
Jens Gustedt 2012

@JensGustedtああ、あなたの答えは私(および他の人)が見逃した非常に重要な点を示しています:型にトラップ値がない限り、符号なし型の場合、少なくとも1ビットを「無駄にする」必要xがありますが、初期化されていない値がありますが、アクセス時の動作はxにレジスタのような動作がなかった場合に定義されます。
Gilles'SO-悪であることをやめ

@Gilles:少なくともclangは、あなたが言及した種類の最適化を行います:(1)(2)(3)
ヴラド

1
clangにそのように処理させることには、どのような実用的な利点がありますか?ダウンストリームコードがの値を使用しない場合x、その値が定義されているかどうかに関係なく、そのコードに対するすべての操作を省略できます。たとえば、次のコードが、ゼロを生成した場合に含まif (volatile1) x=volatile2; ... x = (x+volatile3) & 255;れるx可能性のある0〜255の値に等しく満足する場合volatile1、プログラマーが不要な書き込みを省略できる実装はx、それよりも高品質と見なされるべきだと思います。 ...振る舞う
supercat

...その場合、まったく予測できない方法で。その場合に実装定義のトラップを確実に発生させる実装は、特定の目的では、まだ高品質であると見なされる可能性がありますが、まったく予測できない動作は、ほとんどすべての目的で最低品質の動作のように思えます。
スーパーキャット2018年

16

はい、プログラムがクラッシュする可能性があります。たとえば、CPU割り込みを引き起こす可能性のあるトラップ表現(処理できない特定のビットパターン)が存在する可能性があり、これを処理しないとプログラムがクラッシュする可能性があります。

(後期C11ドラフトの6.2.6.1によると)特定のオブジェクト表現は、オブジェクトタイプの値を表す必要はありません。オブジェクトの格納された値がそのような表現を持ち、文字タイプを持たない左辺値式によって読み取られる場合、動作は未定義です。そのような表現が、文字タイプを持たない左辺値式によってオブジェクトのすべてまたは一部を変更する副作用によって生成される場合、動作は定義されていません。50)このような表現はトラップ表現と呼ばれます。

(この説明は、unsigned intトラップ表現を持つことができるプラットフォームにのみ適用されます。これは、実際のシステムではまれです。詳細についてはコメントを参照し、標準の現在の表現につながる代替の、おそらくより一般的な原因への参照を参照してください。)


3
@VladLazarenko:これはCに関するものであり、特定のCPUではありません。誰でも、それを狂わせる整数のビットパターンを持つCPUを簡単に設計できます。レジスタに「クレイジービット」があるCPUについて考えてみます。
David Schwartz

2
では、整数とx86の場合の動作は明確に定義されていると言えますか?

3
まあ、理論的には、28ビット整数(x86上)のみを使用し、各加算、乗算(など)を処理する特定のコードを追加し、これらの4ビットが未使用になる(またはSIGSEGVを発行する)ことを決定したコンパイラを使用できます。 )。初期化されていない値がこれを引き起こす可能性があります。
eq- 2012

4
誰かが問題を理解していないので、誰かが他の人を侮辱するのは嫌いです。振る舞いが未定義であるかどうかは、完全に規格の内容の問題です。ああ、そしてeqのシナリオについてはまったく実用的なものは何もありません...それは完全に考案されています。
ジムバルター2012

7
@Vlad Lazarenko:Itanium CPUには、整数レジスタごとにNaT(Not a Thing)フラグがあります。NaTフラグは、投機的実行を制御するために使用され、使用前に適切に初期化されていないレジスタに残る可能性があります。NaTビットが設定されたこのようなレジスタから読み取ると、例外が発生します。blogs.msdn.com/b/oldnewthing/archive/2004/01/19/60162.aspx
北欧メインフレーム

13

(この回答はC 1999に対応しています。C2011については、Jens Gustedtの回答を参照してください。)

C標準では、初期化されていない自動保存期間のオブジェクトの値を使用することは未定義の動作であるとは述べていません。C 1999標準では、6.7.8 10で、「自動保存期間を持つオブジェクトが明示的に初期化されていない場合、その値は不確定です」と述べています。(この段落では、静的オブジェクトの初期化方法を定義しているため、初期化されていないオブジェクトは自動オブジェクトのみです。)

3.17.2では、「不定値」を「不特定の値またはトラップ表現のいずれか」と定義しています。3.17.3は、「未指定の値」を「この国際規格がどのインスタンスでどの値を選択するかについての要件を課していない、関連するタイプの有効な値」と定義しています。

したがって、初期化されunsigned int xていない値に不特定の値がある場合は、x -= xゼロを生成する必要があります。それはそれが罠の表現であるかもしれないかどうかという問題を残します。6.2.6.1 5に従って、トラップ値にアクセスすると、未定義の動作が発生します。

一部のタイプのオブジェクトには、浮動小数点数のシグナリングNaNなど、トラップ表現がある場合があります。しかし、符号なし整数は特別です。6.2.6.2に従い、unsigned intのN個の値ビットのそれぞれは2の累乗を表し、値ビットの各組み合わせは0から2 N -1までの値の1つを表します。したがって、符号なし整数は、パディングビット(パリティビットなど)の一部の値が原因でのみトラップ表現を持つことができます。

ターゲットプラットフォームで、unsigned intにパディングビットがない場合、初期化されていないunsigned intはトラップ表現を持つことができず、その値を使用しても未定義の動作が発生することはありません。


xトラップ表現がある場合、トラップするx -= x可能性がありますよね?それでも、余分なビットのない符号なし整数を指摘するための+1は、定義された動作を持っている必要があります-それは明らかに他の答えの反対であり、(引用によると)それは標準が意味するもののようです。
user541686 2012

はい、タイプにxトラップ表現がある場合、トラップするx -= x可能性があります。単にx値として使用されただけでも、トラップされる可能性があります。(x左辺値として使用するのは安全です。オブジェクトへの書き込みは、オブジェクト内にあるトラップ表現の影響を受けません。)
Eric Postpischil 2012

符号なしタイプがトラップ表現を持つことはめったにありません
Jens Gustedt 2012

Raymond Chenの言葉を引用すると、「ia64では、各64ビットレジスタは実際には65ビットです。余分なビットは「notthing」を表す「NaT」と呼ばれます。このビットは、レジスタに有効な値が含まれていない場合に設定されます。浮動小数点NaNの整数バージョンと考えてください。...値がNaTであるレジスタがあり、それを間違った方法で呼吸する場合(たとえば、その値をメモリに保存しようとする場合)、プロセッサはSTATUS_REG_NAT_CONSUMPTION例外を発生させます」。つまり、トラップビットは完全に値の範囲外になる可能性があります。
乾杯とhth。-アルフ

-1ステートメント「ターゲットプラットフォームで、unsigned intにパディングビットがない場合、初期化されていないunsigned intはトラップ表現を持つことができず、その値を使用しても未定義の動作は発生しません。」x64NaTビットのようなスキームを考慮していません。
乾杯とhth。-アルフ

11

はい、未定義です。コードがクラッシュする可能性があります。Cは、一般的な規則に例外を設ける特別な理由がないため、動作は未定義であると述べています。この利点は、未定義の動作の他のすべての場合と同じ利点です。コンパイラーは、これを機能させるために特別なコードを出力する必要はありません。

明らかに、コンパイラーは変数内で「便利」と見なされるガベージ値を使用するだけで、意図したとおりに機能します...そのアプローチの何が問題になっていますか?

なぜそれが起こらないと思いますか?それがまさに採用されたアプローチです。コンパイラはそれを機能させる必要はありませんが、失敗させる必要はありません。


1
ただし、コンパイラはこのための特別なコードを持っている必要はありません。(いつものように)スペースを割り当てるだけで、変数を初期化ないと、正しい動作が得られます。特別なロジックは必要ないと思います。
user541686 2012

7
1)確かに、彼らは持っている可能性があります。しかし、それをより良くするような議論は思いつかない。2)プラットフォームは、初期化されていないメモリの値が信頼できないことを認識しているため、自由に変更できます。たとえば、バックグラウンドで初期化されていないメモリをゼロにして、ゼロ化されたページを必要なときに使用できるようにすることができます。(これが発生するかどうかを検討してください:1)減算する値を読み取り、3を取得します。2)初期化されていないため、ページがゼロになり、値が0に変更されます。3)アトミック減算を実行し、ページを割り当てて、値-3。おっと。)
David Schwartz

2
-1あなたはあなたの主張をまったく正当化しないからです。コンパイラがメモリ位置に書き込まれた値を取得するだけであると期待することが有効な場合があります。
Jens Gustedt 2012

1
@JensGustedt:あなたのコメントがわかりません。明確にしていただけますか?
David Schwartz

3
あなたはそれを参照せずに、一般的なルールがあると主張するだけだからです。そのため、これは「権限による証明」の試みにすぎず、SOに期待するものではありません。そして、なぜこれが不特定の値になり得ないのかを効果的に議論しなかったためです。一般的な場合、これがUBである唯一の理由は、xとして宣言できることregisterです。つまり、そのアドレスが取得されることはありません。あなたがそれを知っていたかどうかはわかりませんが(もしあなたがそれを効果的に隠していたなら)、正しい答えはそれを述べなければなりません。
Jens Gustedt 2012

7

初期化されていない、またはその他の理由で不確定な値を保持している任意のタイプの変数の場合、その値を読み取るコードには以下が適用されます。

  • 変数が自動保存期間を有する場合、そのアドレスが取られていない、コードが常に未定義の動作[1]を呼び出します。
  • それ以外の場合、システムが特定の変数タイプのトラップ表現をサポートしている場合、コードは常に未定義の動作を呼び出します[2]。
  • それ以外の場合、トラップ表現がない場合、変数は不特定の値を取ります。この不特定の値が変数が読み取られるたびに一貫しているという保証はありません。ただし、トラップ表現ではないことが保証されているため、未定義の動作を呼び出さないことが保証されています[3]。

    この値は、プログラムクラッシュを引き起こすことなく安全に使用できますが、そのようなコードはトラップ表現を備えたシステムに移植できません。


[1]:C11 6.3.2.1:

左辺値が、レジスタストレージクラスで宣言された可能性のある自動ストレージ期間のオブジェクトを指定し(アドレスが取得されなかった)、そのオブジェクトが初期化されていない(イニシャライザで宣言されておらず、使用前に割り当てが実行されていない)場合)、動作は未定義です。

[2]:C11 6.2.6.1:

特定のオブジェクト表現は、オブジェクトタイプの値を表す必要はありません。オブジェクトの格納された値がそのような表現を持ち、文字タイプを持たない左辺値式によって読み取られる場合、動作は未定義です。そのような表現が、文字タイプを持たない左辺値式によってオブジェクトのすべてまたは一部を変更する副作用によって生成される場合、動作は定義されていません。50)このような表現はトラップ表現と呼ばれます。

[3] C11:

3.19.2
不定値
または不特定の値またはトラップ表現

3.19.3 この国際規格がどのインスタンスでもどの値を選択するかについての要件を課していない、関連するタイプの
不特定の値の
有効な値
注不特定の値をトラップ表現にすることはできません。

3.19.4
トラップ表現
オブジェクトタイプの値を表す必要のないオブジェクト表現


3
@Vality現実の世界では、すべてのコンピューターの99.9999%が、トラップ表現のない2の補数CPUです。したがって、トラップ表現は標準ではなく、そのような実世界のコンピューターでの動作について議論することは非常に重要です。非常にエキゾチックなコンピューターが標準であると想定することは役に立ちません。現実の世界でのトラップ表現は非常にまれであるため、標準にトラップ表現という用語が存在することは、ほとんどの場合、1980年代から継承された標準的な欠陥と見なされます。1の補数および符号と大きさのコンピューターのサポートも同様です。
Lundin 2017年

3
ちなみに、これはstdint.h、ネイティブタイプのCの代わりに常に使用する必要がある優れた理由ですstdint.h。2の補数を強制し、パディングビットを使用しないためです。言い換えれば、stdint.hタイプががらくたでいっぱいになることは許可されていません。
Lundin 2017年

2
この場合も、欠陥レポートに対する委員会の回答は次のように述べています。「質問2の答えは、不確定な値に対して実行された操作は、結果として不確定な値になるということです。」「質問3の答えは、ライブラリ関数を不確定な値で使用すると、未定義の動作を示すということです。」
Antti Haapala 2017年

2
DR 451と260
アンティHaapala

1
@AnttiHaapalaはい私はそのDRを知っています。この答えと矛盾することはありません。初期化されていないメモリ位置を読み取るときに不確定な値が得られる場合があり、それは必ずしも毎回同じ値であるとは限りません。しかし、それは未定義の動作であり、未定義の動作ではありません。
Lundin 2017年

2

多くの回答は、初期化されていないレジスタアクセスをトラップするプロセッサに焦点を当てていますが、UBを悪用する特別な努力をしないコンパイラを使用すると、そのようなトラップがないプラットフォームでも奇妙な動作が発生する可能性があります。コードを考えてみましょう:

ロードとストア以外のすべての命令が32ビットレジスタで動作するARMのようなプラットフォーム用のコンパイラは、次と同等の方法でコードを合理的に処理する可能性があります。

いずれかの揮発性読み取りでゼロ以外の値が生成された場合、r0には0〜65535の範囲の値がロードされます。それ以外の場合は、関数が呼び出されたときに保持されていたもの(つまり、xに渡された値)が生成されます。これは、0..65535の範囲の値ではない可能性があります。この標準には、タイプがuint16_tであるが、値が0..65535の範囲外である値の動作を説明する用語がありません。ただし、そのような動作を生成する可能性のあるアクションはUBを呼び出すということを除きます。


面白い。それで、あなたは受け入れられた答えが間違っていると言っていますか?それとも、理論的には正しいと言っていますが、実際にはコンパイラーはもっと奇妙なことをするかもしれませんか?
user541686 2016

@Mehrdad:実装では、UBがない場合に可能な動作の範囲を超える動作をするのが一般的です。標準が部分的に不確定な値の概念を認識し、その「割り当てられた」ビットが最悪の場合は指定されていない方法で動作するが、追加の上位ビットが非決定論的に動作する場合(たとえば、上記の関数の結果は、タイプの変数に格納されますuint16_t。その変数は、123として読み取られることもあれば、6553623として読み取られることもあります。アップ結果の端が無視されている場合...
supercat

...または、それが読み取られる可能性のあるすべての方法で要件を満たす最終結果が得られるように使用された場合、部分的に不確定な値の存在は問題にはなりません。他方、規格には、規格がいかなる行動要件を課すであろういかなる状況においても部分的に不確定な値の存在を可能にするものは何もない。
スーパーキャット2016

あなたが説明していることは、受け入れられた答えにあるものとまったく同じであるように私には思えます-変数がで宣言されている可能性がある場合register、動作を潜在的に未定義にする余分なビットが含まれている可能性があります。それはまさにあなたが言っていることですよね?
user541686 2016

@Mehrdad:受け入れられた答えは、レジスタに余分な「初期化されていない」状態があり、初期化されていないレジスタがロードされた場合にトラップするアーキテクチャに焦点を当てています。このようなアーキテクチャは存在しますが、一般的ではありません。私はシナリオを説明しますありふれたハードウェアが、C標準で想定されているものの範囲外の動作を示す可能性があるが、コンパイラーが独自の奇抜さをミックスに追加しない場合は、便利に制約。たとえば、関数に実行する操作を選択するパラメーターがあり、一部の操作は有用なデータを返すが、他の操作は返さない場合、...
supercat 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.