Cデータ型はどのように「ほとんどのコンピューターで直接サポートされている」のですか?


114

私はK&Rの「Cプログラミング言語」を読んでいて、この声明に出くわしました[はじめに、p。3]:

Cが提供するデータ型と制御構造はほとんどのコンピューター直接サポートされているため、自己完結型プログラムの実装に必要なランタイムライブラリはごくわずかです。

太字のステートメントはどういう意味ですか?コンピューターで直接サポートされていないデータ型または制御構造の例はありますか?


1
最近では、C言語は複雑な算術をサポートしていますが、コンピューターはデータ型として複素数を直接サポートしていないため、当初はサポートされていませんでした。
Jonathan Leffler、2015年

12
実際、これは歴史的には逆でした。Cは、当時利用可能なハードウェアの操作とタイプから設計されました。
Basile Starynkevitch、2015年

2
ほとんどのコンピューターは、10進浮動小数点数を直接ハードウェアでサポートしていません
PlasmaHH

3
@MSalters:「コンピュータで直接サポートされていないデータ型または制御構造の例はありますか?」という質問に対して、私はいくつかの方向にヒントを与えようとしていました。これはK&Rに限定されるとは解釈しませんでした
PlasmaHH 2015年

11
Stack Overflowがリリースされてから6年以上経過した後、これはどのように複製されないのですか?
Peter Mortensen

回答:


143

はい、直接サポートされていないデータ型があります。

多くの組み込みシステムでは、ハードウェア浮動小数点ユニットはありません。したがって、次のようなコードを書くと、

float x = 1.0f, y = 2.0f;
return x + y;

それはこのようなものに翻訳されます:

unsigned x = 0x3f800000, y = 0x40000000;
return _float_add(x, y);

次に、コンパイラまたは標準ライブラリがの実装を提供する_float_add()必要があります。これは、組み込みシステムのメモリを占有します。非常に小さなシステムでバイト数を数えている場合、これは加算されます。

もう1つの一般的な例は、64ビット整数(long long1999年以降のC標準)で、32ビットシステムでは直接サポートされていません。古いSPARCシステムは整数乗算をサポートしていなかったため、乗算はランタイムによって提供されなければなりませんでした。他の例があります。

他の言語

比較すると、他の言語にはより複雑なプリミティブがあります。

たとえば、Lispシンボルは、Luaのテーブル、Pythonの文字列、Fortranの配列などと同様に、多くのランタイムサポートを必要とします。Cの同等の型は通常、標準ライブラリの一部ではない(標準のシンボルやテーブルがない)か、はるかに単純であり、ランタイムサポートをあまり必要としません(Cの配列は基本的に単なるポインタであり、nullで終了する文字列はほぼ同じです)。

制御構造

Cから欠落している注目すべき制御構造は、例外処理です。非ローカル出口はsetjmp()and longjmp()に限定されており、プロセッサー状態の特定の部分を保存および復元するだけです。比較すると、C ++ランタイムはスタックをウォークして、デストラクタと例外ハンドラを呼び出す必要があります。


2
基本的には単なるポインタ...むしろ、基本的にはメモリの生のチャンクです。たとえそれが意味のない選択であるとしても、答えはとにかく良いです。
Deduplicator

2
文字列ターミネーターはほとんどのプロセッサーの「ジャンプ・イフ・ゼロ」操作に適合し、したがって他の可能な文字列実装よりもわずかに速いため、ヌル終了文字列には「ハードウェア・サポート」があると主張できます。
Peteris、2015年

1
Cが単純にasmにマッピングするように設計されている方法を拡張するために、私自身の回答を投稿しました。
Peter Cordes

1
コロケーション「配列は基本的に単なるポインタ」を使用しないでください。OPのような初心者を真剣に、ひどく誤解させる可能性があります。「配列はハードウェアレベルでポインタを使用して直接実装される」という線に沿ったものが、より良いIMOになるでしょう。
常磁性クロワッサン2015年

1
@TheParamagneticCroissant:このコンテキストでは適切だと思います...明快さは精度を犠牲にします。
ディートリッヒエップ2015年

37

実際、この紹介の内容は、カーニガンとリッチーが本の初版で最初に書いた1978年以降、それほど変わっていないと思います。それらは、現代よりも当時のCの歴史と進化に言及しています。実装。

コンピュータは基本的にメモリバンクと中央処理装置にすぎず、各プロセッサはマシンコードを使用して動作します。各プロセッサの設計の一部は、アセンブリ言語と呼ばれる命令セットアーキテクチャであり、人間が読めるニーモニックのセットからマシンコード(すべて数字)に1対1でマッピングされます。

C言語の作成者(およびその直前のBおよびBCPL言語)は、できるだけ効率的にアセンブリにコンパイルされる言語で構文を定義することに熱心でした...実際、ターゲットの制限によって強制されましたハードウェア。他の答えが指摘しているように、これには、関連する分岐(GOTOおよびその他のCのフロー制御)、移動(割り当て)、論理演算(&| ^)、基本的な算術(加算、減算、増分、減分)、およびメモリアドレス指定(ポインター)。良い例は、Cのプリ/ポストインクリメントおよびデクリメント演算子です。これらは、特に一度コンパイルされた単一のオペコードに直接変換できるため、ケントンプソンによってB言語に追加されたと思われます。

これは、著者が「ほとんどのコンピュータで直接サポートされている」と言ったときに意味したものです。他の言語に、直接サポートされていない型や構造が含まれているという意味ではありませんでした。つまり、設計により、 Cの構成要素がアセンブリに最も直接(場合によっては文字どおり直接)変換されました。

基になるアセンブリとの密接な関係は、構造化プログラミングに必要なすべての要素を提供しながら、Cの早期採用につながったものであり、コンパイルされたコードの効率が依然として重要である環境で今日の人気の高い言語を維持しています。

言語の歴史に関する興味深い記事については、C言語の開発-デニス・リッチーを参照してください


14

簡単に言えば、Cでサポートされているほとんどの言語構造はターゲットコンピュータのマイクロプロセッサでもサポートされているため、コンパイルされたCコードはマイクロプロセッサのアセンブリ言語に非常にうまく効率的に変換されるため、コードが小さくなり、フットプリントも小さくなります。

長い回答には、アセンブリ言語の知識が少し必要です。Cでは、次のようなステートメント:

int myInt = 10;

アセンブリでは次のようなものに変換されます。

myInt dw 1
mov myInt,10

これをC ++のようなものと比較してください。

MyClass myClass;
myClass.set_myInt(10);

結果のアセンブリ言語コード(MyClass()の大きさに依存します)は、何百ものアセンブリ言語行を追加する可能性があります。

実際にアセンブリ言語でプログラムを作成することなく、純粋なCはおそらく、プログラムを作成できる「最も細く」、「最もタイト」なコードです。

編集

私の回答に対するコメントを踏まえて、私は自分の正気のためだけにテストを実行することにしました。次のような「test.c」というプログラムを作成しました。

#include <stdio.h>

void main()
{
    int myInt=10;

    printf("%d\n", myInt);
}

これをgccを使用してアセンブリにコンパイルしました。次のコマンドラインを使用してコンパイルしました。

gcc -S -O2 test.c

結果のアセンブリ言語は次のとおりです。

    .file   "test.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "%d\n"
    .section    .text.unlikely,"ax",@progbits
.LCOLDB1:
    .section    .text.startup,"ax",@progbits
.LHOTB1:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB24:
    .cfi_startproc
    movl    $10, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    jmp __printf_chk
    .cfi_endproc
.LFE24:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE1:
    .section    .text.startup
.LHOTE1:
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

次に、クラスを定義して「test.c」と同じものを出力する「test.cpp」というファイルを作成します。

#include <iostream>
using namespace std;

class MyClass {
    int myVar;
public:
    void set_myVar(int);
    int get_myVar(void);
};

void MyClass::set_myVar(int val)
{
    myVar = val;
}

int MyClass::get_myVar(void)
{
    return myVar;
}

int main()
{
    MyClass myClass;
    myClass.set_myVar(10);

    cout << myClass.get_myVar() << endl;

    return 0;
}

次のコマンドを使用して、同じ方法でコンパイルしました。

g++ -O2 -S test.cpp

結果のアセンブリファイルは次のとおりです。

    .file   "test.cpp"
    .section    .text.unlikely,"ax",@progbits
    .align 2
.LCOLDB0:
    .text
.LHOTB0:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9set_myVarEi
    .type   _ZN7MyClass9set_myVarEi, @function
_ZN7MyClass9set_myVarEi:
.LFB1047:
    .cfi_startproc
    movl    %esi, (%rdi)
    ret
    .cfi_endproc
.LFE1047:
    .size   _ZN7MyClass9set_myVarEi, .-_ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE0:
    .text
.LHOTE0:
    .section    .text.unlikely
    .align 2
.LCOLDB1:
    .text
.LHOTB1:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9get_myVarEv
    .type   _ZN7MyClass9get_myVarEv, @function
_ZN7MyClass9get_myVarEv:
.LFB1048:
    .cfi_startproc
    movl    (%rdi), %eax
    ret
    .cfi_endproc
.LFE1048:
    .size   _ZN7MyClass9get_myVarEv, .-_ZN7MyClass9get_myVarEv
    .section    .text.unlikely
.LCOLDE1:
    .text
.LHOTE1:
    .section    .text.unlikely
.LCOLDB2:
    .section    .text.startup,"ax",@progbits
.LHOTB2:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB1049:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $10, %esi
    movl    $_ZSt4cout, %edi
    call    _ZNSolsEi
    movq    %rax, %rdi
    call    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
    xorl    %eax, %eax
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE1049:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE2:
    .section    .text.startup
.LHOTE2:
    .section    .text.unlikely
.LCOLDB3:
    .section    .text.startup
.LHOTB3:
    .p2align 4,,15
    .type   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, @function
_GLOBAL__sub_I__ZN7MyClass9set_myVarEi:
.LFB1056:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZStL8__ioinit, %edi
    call    _ZNSt8ios_base4InitC1Ev
    movl    $__dso_handle, %edx
    movl    $_ZStL8__ioinit, %esi
    movl    $_ZNSt8ios_base4InitD1Ev, %edi
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    jmp __cxa_atexit
    .cfi_endproc
.LFE1056:
    .size   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, .-_GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE3:
    .section    .text.startup
.LHOTE3:
    .section    .init_array,"aw"
    .align 8
    .quad   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .hidden __dso_handle
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

はっきりとわかるように、結果のアセンブリファイルは、C ++ファイルよりもC ++ファイルよりもはるかに大きくなります。他のすべてのものを切り取って、Cの「メイン」とC ++の「メイン」を比較するだけでも、余分なものがたくさんあります。


14
その「C ++コード」はC ++ではありません。またMyClass myClass { 10 }、C ++ などの実際のコードは、まったく同じアセンブリにコンパイルされる可能性が非常に高くなります。最新のC ++コンパイラは、抽象化のペナルティを排除しました。その結果、Cコンパイラに勝ることがよくあります。たとえば、Cの抽象化ペナルティqsortは現実ですが、C ++にstd::sortは、基本的な最適化さえ行っても抽象化ペナルティはありません。
MSalters 2015年

1
あなたは簡単にIDAを使用して見ることができるプロ、ほとんどのC ++構文コンパイルダウンCで手動でそれをやってと同じことに、コンストラクタとdtorsは些細なオブジェクトのためのインライン化を取得し、将来の最適化が適用される
paulm

7

K&Rは、ほとんどのC式(技術的な意味)が、サポートライブラリへの関数呼び出しではなく、1つまたはいくつかのアセンブリ命令にマップされることを意味します。通常の例外は、ハードウェアdiv命令のないアーキテクチャの整数除算、またはFPUのないマシンの浮動小数点です。

引用があります:

Cは、アセンブリ言語の柔軟性と能力を、アセンブリ言語の使いやすさと組み合わせています。

ここにあります。「アセンブリ言語の便利さと表現力を備えたアセンブリ言語の速度」のような別のバリエーションを覚えていると思いました。)

long intは通常、ネイティブマシンのレジスタと同じ幅です。

一部の高水準言語は、データ型の正確な幅を定義しており、すべてのマシンでの実装は同じように機能する必要があります。Cではありません。

x86-64で128ビットの整数、または一般的な場合は任意のサイズのBigIntegerで作業する場合は、そのための関数のライブラリが必要です。すべてのCPUは負の整数の2進数表現として2の補数を使用するようになりましたが、Cが設計されたときはそうではありませんでした。(これが、2の補数でないマシンで異なる結果をもたらすいくつかの事柄が、C標準では技術的に定義されていない理由です。)

データまたは関数へのCポインターは、アセンブリアドレスと同じように機能します。

参照カウントされた参照が必要な場合は、自分で行う必要があります。ポインターが指しているオブジェクトの種類に応じて異なる関数を呼び出すc ++仮想メンバー関数が必要な場合、C ++コンパイラーはcall、固定アドレスの命令だけではなく、はるかに多くを生成する必要があります。

文字列は単なる配列です

ライブラリ関数以外では、提供される文字列操作は文字の読み取り/書き込みのみです。連結なし、部分文字列なし、検索なし。(文字'\0'列は、ポインタ+長さではなく、8ビット整数のNUL終了()配列として格納されるため、部分文字列を取得するには、NULを元の文字列に書き込む必要があります。)

CPUには、文字列検索関数で使用するように設計された命令が含まれている場合がありますが、通常は、実行される命令ごとに1バイトをループで処理します。(または、x86 repプレフィックスを使用します。Cがx86で設計されている場合、文字列検索または比較は、ライブラリ関数呼び出しではなく、ネイティブ操作になります。)

他の多くの回答は、例外処理、ハッシュテーブル、リストなど、ネイティブでサポートされていないものの例を示しています。K&Rの設計哲学が、Cがこれらをネイティブに持たない理由です。


「K&Rは、ほとんどのC式(技術的な意味)が、サポートライブラリへの関数呼び出しではなく、1つまたはいくつかのアセンブリ命令にマップされることを意味します。」これは非常に直感的な説明です。ありがとう。
gwg 2015年

1
私は「フォンノイマン言語」という用語に出くわしました(en.wikipedia.org/wiki/Von_Neumann_programming_languages)。それはまさにCが何であるかです。
Peter Cordes、2015

1
これがまさに私がCを使用する理由です。しかし、Cを学んで驚いたのは、幅広いハードウェアに対して効率的になることを試みることによって、最新のハードウェアでは不十分で非効率になることがあるということです。たとえば、役に立たない、信頼できる方法で、integer-overflow-in-cmulti-word-addition-using-the-carry-flagを検出します。
Zボソン2015

6

プロセスのアセンブリ言語は通常、ジャンプ(移動)、ステートメント、移動ステートメント、バイナリ関節炎(XOR、NAND、AND ORなど)、メモリフィールド(またはアドレス)を扱います。メモリを命令とデータの2つのタイプに分類します。これは、すべてのアセンブリ言語についてです(私は、アセンブリプログラマがそれ以上のものがあると主張するでしょうが、それは一般的にこれに要約されます)。Cは、この単純さに非常に似ています。

Cは、代数と算術を組み合わせることです。

Cは、アセンブリーの基本(プロセッサーの言語)をカプセル化します。「Cが提供するデータ型と制御構造はほとんどのコンピューターで直接サポートされているため」よりもおそらく正しい記述です。


5

誤解を招く比較に注意してください

  1. この声明は、少なくとも主流の高級言語では、時代遅れになった「ランタイムライブラリ」という概念に依存しています。(それでも、最小の組み込みシステムに関連しています。)ランタイムは、その言語のプログラムが実行に必要な最小限のサポートです(ライブラリによって提供される関数を明示的に呼び出すのではなく)。 。
  2. 対照的に、現代の言語はランタイムと標準ライブラリ区別しない傾向があり、標準ライブラリは非常に広範囲にわたることがよくあります。
  3. K&R本の当時、Cには標準ライブラリさえありませんでした。むしろ、利用可能なCライブラリはUnixの種類によってかなり異なります。
  4. ステートメントを理解するために、標準ライブラリの言語(他の回答で言及されているLuaやPythonなど)と比較するのではなく、より多くの組み込み構造を持つ言語(他の言語で言及されている旧式のLISPや旧式のFORTRANなど)と比較してください。回答)。他の例は、BASIC(LISPのようなインタラクティブ)またはPASCAL(FORTRANのようにコンパイルされた)で、どちらも(とりわけ)入出力機能が言語自体に組み込まれています。
  5. 対照的に、ランタイムのみを使用し、ライブラリを使用していないCプログラムから計算結果を取得する標準的な方法はありません。

一方、最新のほとんどの言語は、ガベージコレクションなどの機能を提供する専用ランタイム環境内で実行されます。
Nate CK、

5

コンピューターで直接サポートされていないデータ型または制御構造の例はありますか?

C言語でのすべての基本的なデータ型とその操作は、ループすることなく1つまたはいくつかの機械語命令で実装できます。これらは(実質的にすべての)CPUで直接サポートされています。

いくつかの一般的なデータ型とその操作には、何十もの機械語命令が必要か、またはいくつかのランタイムループの反復、あるいはその両方が必要です。

多くの言語には、そのような型とその操作のための特別な省略構文があります-Cでそのようなデータ型を使用するには、通常、さらに多くのコードを入力する必要があります。

このようなデータ型と操作には次のものがあります。

  • 任意長のテキスト文字列操作-連結、部分文字列、他の文字列で初期化された変数への新しい文字列の割り当てなど( 's = "Hello World!"; s =(s + s)[2:-2] 'Pythonで)
  • セット
  • C ++およびその他すべてのオブジェクト指向プログラミング言語のように、仮想デストラクタがネストされたオブジェクト
  • 2D行列の乗算と除算。線形システムを解く(MATLABおよび多くの配列プログラミング言語では "C = B / A; x = A \ b")
  • 正規表現
  • 可変長配列-特に、配列の最後に項目を追加する場合、(場合によっては)より多くのメモリを割り当てる必要があります。
  • 実行時に型を変更する変数の値を読み取る-浮動小数点の場合もあれば、文字列の場合もあります
  • 連想配列(「マップ」または「辞書」と呼ばれることが多い)
  • リスト
  • 比率( "(+ 1/3 2/7)"はLispで "13/21" 与える)
  • 任意精度の演算(「bignum」と呼ばれることが多い)
  • データを印刷可能な表現に変換する(JavaScriptの ".tostring"メソッド)
  • 飽和固定小数点数(多くの場合、組み込みCプログラムで使用されます)
  • 実行時に入力された文字列を式であるかのように評価します(多くのプログラミング言語では "eval()")。

これらの操作はすべて、何十もの機械語命令を必要とするか、ほぼすべてのプロセッサでいくつかのランタイムループを繰り返す必要があります。

何十もの機械語命令またはループも必要とする一般的な制御構造には、次のものがあります。

  • 閉鎖
  • 継続
  • 例外
  • 怠惰な評価

Cまたは他の言語で記述されているかどうかに関係なく、プログラムがそのようなデータ型を操作するとき、CPUは最終的にそれらのデータ型を操作するために必要な命令を実行する必要があります。これらの指示は、多くの場合「ライブラリ」に含まれています。Cを含むすべてのプログラミング言語には、デフォルトですべての実行可能ファイルに含まれている各プラットフォーム用の「ランタイムライブラリ」があります。

コンパイラを作成するほとんどの人は、「言語に組み込まれている」すべてのデータ型を操作するための命令をランタイムライブラリに入れます。Cが持っていないので任意のランよりもCランタイムライブラリが小さくなります-言語に組み込まれて上記のデータ型と演算や制御構造のを、それらのどれもがCランタイムライブラリに含まれていません上記の他のものが言語に組み込まれている他のプログラミング言語のタイムライブラリ。

プログラマーが、C言語または任意の他の言語で、「言語に組み込まれていない」他のデータ型を操作することを望む場合、そのプログラマーは通常、そのプログラムに追加のライブラリーを含めるようにコンパイラーに指示します。 (「依存関係を回避する」ために)これらの操作のさらに別の実装をプログラムに直接書き込みます。


Lispの実装が(+ 1/3 2/7)を3/21と評価する場合、特に創造的な実装が必要だと思います...
RobertB

4

の組み込みデータ型はC何ですか?彼らはのようなものですintchar* intfloat、配列等...これらのデータ型は、CPUによって理解されています。CPUは、配列の操作方法、ポインターの逆参照方法、ポインター、整数、浮動小数点数の演算方法を認識しています。

しかし、より高いレベルのプログラミング言語に行くと、抽象的なデータ型とより複雑な構造が組み込まれています。たとえば、C ++プログラミング言語の組み込みクラスの膨大な配列を見てください。CPUはクラス、オブジェクト、または抽象データ型を理解しないため、C ++ランタイムはCPUと言語の間のギャップを埋めます。これらは、ほとんどのコンピュータで直接サポートされていないデータ型の例です。


2
x86は、すべてではなく一部のアレイで動作することを認識しています。要素サイズが大きい場合や異常な場合は、配列インデックスをポインタオフセットに変換する整数演算を実行する必要があります。そして、他のプラットフォームでは、これは常に必要です。そして、CPUがC ++クラスを理解しないという考えはおかしいです。Cの構造体のように、それは単なるポインタオフセットです。そのためのランタイムは必要ありません。
MSalters 2015年

@MSaltersはい、ただし、iostreamなどの標準ライブラリクラスの実際のメソッドは、コンパイラによって直接サポートされるのではなく、ライブラリ関数です。しかし、彼らが比較している可能性が高い高水準言語はC ++ではなく、FORTRANやPL / Iなどの現代的な言語でした。
Random832 2015年

1
仮想メンバー関数を持つC ++クラスは、単なる構造体へのオフセット以上のものに変換されます。
Peter Cordes

4

それはコンピュータに依存します。Cが発明されたPDP-11では、longサポートが不十分でした(32ビット操作のすべてではなく一部をサポートする、購入可能なオプションのアドオンモジュールがありました)。元のIBM PCを含め、16ビットシステムのさまざまな程度で同じことが当てはまります。同様に、32ビットマシンまたは32ビットプログラムでの64ビット操作の場合も同様ですが、K&R本の執筆時のC言語には64ビット操作はまったくありませんでした。そしてもちろん、80年代と90年代には多くのシステム(386や一部の486プロセッサを含む)があり、今日の組み込みシステムでさえ、浮動小数点演算(floatまたはdouble)を直接サポートしていません。

よりエキゾチックな例として、一部のコンピュータアーキテクチャは「ワード指向」のポインタ(メモリ内の2バイトまたは4バイトの整数を指す)のみをサポートし、追加のオフセットフィールドを追加することによってバイトポインタ(char *またはvoid *)を実装する必要がありました。この質問は、そのようなシステムについてのいくつかの詳細に入ります。

それが参照する「ランタイムライブラリ」関数は、マニュアルに表示されるものではありませんが、マシンでサポートされていない基本的なタイプの演算を実装するために使用される最新のコンパイラのランタイムライブラリは、このような関数です。K&R自身が参照していたランタイムライブラリは、Unix Heritage SocietyのWebサイトにあります-の除算を実装するために使用される(当時存在しなかった、同じ名前のC関数とは異なる)のような関数を見ることができますPDP-11がアドオンでもサポートしなかった32ビット値、および関数の呼び出しと戻りを管理するためにスタックのレジスタを保存および復元する(およびcsv.cでも)。ldivcsvcret

彼らはまた、CPUの基になるポインターのサポートにマップされなかった配列のセマンティクスを持つFORTRANなどの他の現代の言語とは異なり、基になるマシンで直接サポートされない多くのデータ型をサポートしないという選択についても言及している可能性があります。 Cの配列。C配列には常にゼロのインデックスが付けられ、すべてのランクで常に既知のサイズであるという事実がありますが、最初の意味は、配列のインデックス範囲またはサイズを格納する必要がなく、それらにアクセスするためのランタイムライブラリ関数が必要ないことです。コンパイラーは必要なポインター演算を単純にハードコーディングできます。


3

ステートメントは、Cのデータと制御構造がマシン指向であることを単に意味します。

ここで考慮すべき2つの側面があります。1つは、C言語にデータ型の定義方法に自由度を許可する定義(ISO標準)があることです。これは、C言語の実装がマシンに合わせて調整されていることを意味します。Cコンパイラーのデータ型は、コンパイラーがターゲットとするマシンで使用可能なものと一致します。これは、言語がそのための自由度を持っているためです。マシンが36ビットなどの異常なワードサイズを持っている場合、タイプintまたはそれlongに適合させることができます。それintがちょうど32ビットであることを前提とするプログラムは壊れます。

第二に、そのような移植性の問題のために、第二の効果があります。ある意味で、K&Rの声明は、一種の自己実現的予言、あるいはおそらくその逆になっています。つまり、新しいプロセッサの実装者は、Cコンパイラをサポートする熱心な必要性を認識しており、「すべてのプロセッサが80386のように見える」と想定するCコードがたくさんあることを知っています。アーキテクチャは、Cを念頭に置いて設計されています。Cだけでなく、Cの移植性に関する一般的な誤解も念頭に置いて設計されています。9ビットバイトのマシンや、一般的な目的で使用するマシンを導入することはできません。タイプを想定するプログラムchar正確に8ビット幅です。移植性の専門家によって書かれた一部のプログラムのみが機能し続けます。ツールチェーン、カーネル、ユーザースペース、および有用なアプリケーションを備えた完全なシステムを、妥当な労力でまとめるにはおそらく不十分です。言い換えると、ハードウェアは、移植性のない多くのCプログラムが作成された他のハードウェアのように見えるため、Cタイプはハードウェアから利用可能なもののように見えます。

コンピューターで直接サポートされていないデータ型または制御構造の例はありますか?

多くの機械語で直接サポートされていないデータ型:倍精度整数。リンクされたリスト; ハッシュ表; 文字列。

ほとんどの機械語で直接サポートされていない制御構造:ファーストクラスの継続。コルーチン/スレッド; 発生器; 例外処理。

これらはすべて、多数の汎用命令を使用して作成されたかなりのランタイムサポートコードと、より基本的なデータ型を必要とします。

Cには、一部のマシンでサポートされていないいくつかの標準データ型があります。C99以降、Cには複素数があります。これらは2つの浮動小数点値から作成され、ライブラリルーチンで動作するように作成されています。一部のマシンには、浮動小数点ユニットがまったくありません。

一部のデータ型については明確ではありません。マシンが1つのレジスタをベースアドレスとして使用し、別のレジスタをスケーリングされた変位として使用してメモリのアドレス指定をサポートしている場合、それは配列が直接サポートされるデータ型であることを意味しますか?

また、浮動小数点といえば、IEEE 754浮動小数点という標準化があります。Cコンパイラにdoubleプロセッサがサポートする浮動小数点形式に同意する理由は、両者が同意するように作られただけでなく、その表現に独立した標準があるためです。


2

などのこと

  • ほとんどすべての関数型言語で使用されるリスト

  • 例外

  • 連想配列(マップ)-PHPやPerlなどに含まれています。

  • ガベージコレクション

  • 多くの言語に含まれているが、CPUで直接サポートされていないデータ型/制御構造。


2

直接サポートされるとは、プロセッサの命令セットに効率的にマッピングされることを理解する必要があります。

  • 長整数型(拡張算術ルーチンが必要な場合があります)と短整数型サイズ(マスキングが必要な場合があります)を除き、整数型の直接サポートがルールです。

  • 浮動小数点型を直接サポートするには、FPUが利用可能である必要があります。

  • ビットフィールドの直接サポートは例外です。

  • 構造体と配列は、ある程度直接サポートされているアドレス計算を必要とします。

  • ポインターは常に間接アドレッシングを介して直接サポートされます。

  • goto / if / while / for / doは、無条件/条件付きブランチによって直接サポートされています。

  • ジャンプテーブルを適用すると、スイッチを直接サポートできます。

  • 関数呼び出しは、スタック機能によって直接サポートされます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.