web-dev-qa-db-ja.com

ポインタのサイズとアーキテクチャ

通常のデスクトップPCで単純なC++プログラムを実行して基本的なテストを実行することにより、任意のタイプのポインター(関数へのポインターを含む)のサイズがターゲットアーキテクチャビットと等しいと仮定するのはもっともらしいようです。

たとえば、32ビットアーキテクチャでは-> 4バイト、64ビットアーキテクチャでは-> 8バイトです。

それを読んだ覚えがありますが、一般的にはそうではありません!

だから私はそのような状況は何だろうと思っていましたか?

  • 他のデータ型へのポインターのサイズと比較したデータ型へのポインターのサイズの同等性のため
  • 関数へのポインターのサイズと比較した、データ型へのポインターのサイズが等しい場合
  • ターゲットアーキテクチャへのポインタのサイズが等しい場合
19
AKL

いいえ、想定することは合理的ではありません。この仮定を行うと、バグが発生する可能性があります。

CまたはC++のポインター(および整数型)のサイズは、最終的にはCまたはC++の実装によって決定されます。通常のCまたはC++の実装は、対象とするアーキテクチャとオペレーティングシステムの影響を大きく受けますが、実行速度以外の理由、たとえば、メモリ使用量の削減の目標、書き込まれていないコードのサポートなどの理由で、型のサイズを選択する場合があります任意の型サイズに完全に移植可能であるか、または大きな整数のより簡単な使用をサポートしている。

メモリ使用量が少ないプログラムをビルドする目的で、64ビットシステムをターゲットとするが32ビットポインターを提供するコンパイラを見たことがあります。 (ポインターを使用して多くの接続と参照を持つ多くの構造体を使用するため、ポインターのサイズがメモリ消費のかなりの要因であることが確認されていました。)ポインターのサイズが64ビットレジスターと等しいという仮定で記述されたソースコードサイズが壊れます。

18

一般に、任意のタイプのポインタ(関数へのポインタを含む)のサイズは、ターゲットアーキテクチャのビットと等しいと想定するのが妥当です。

依存します。メモリ消費量をすばやく見積もる場合は、これで十分です。

(関数へのポインタを含む)

しかし、ここに重要な注意点があります。ほとんどのポインターは同じサイズになりますが、関数ポインターは異なる場合があります。 void*が関数ポインタを保持できるとは限りません。少なくとも、これはCには当てはまります。C++については知りません。

では、そのような状況はどうなるのでしょうか。

それが異なる理由はたくさんあります。プログラムの正確さがこのサイズに依存している場合は、そのような仮定を行うことは決して問題ではありません。代わりに確認してください。それはまったく難しいことではありません。

このマクロを使用して、Cでコンパイル時にそのようなことを確認できます。

#include <assert.h>
static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes");

コンパイルすると、エラーメッセージが表示されます。

$ gcc main.c 
In file included from main.c:1:
main.c:2:1: error: static assertion failed: "Pointers are assumed to be exactly 4 bytes"
 static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes");
 ^~~~~~~~~~~~~

C++を使用している場合、#include <assert.h>はC++のキーワードであるため、static_assertをスキップできます。 (Cではキーワード_Static_assertを使用できますが、見栄えが悪いので、代わりにインクルードとマクロを使用してください。)

これらの2行はコードに含めるのが非常に簡単であるため、プログラムが間違ったポインターサイズで正しく動作しない場合にそうしない理由はありません。

14
klutt

任意のタイプのポインタ(関数へのポインタを含む)の一般的なサイズがターゲットアーキテクチャのビットと等しいと仮定するのは理にかなっていますか?

それは合理的かもしれませんが、確実に正しいわけではありません。ですから、答えは「いいえ」です。ただし、答えが「はい」であることがわかっている場合(移植性については心配していません)」です。

潜在的に:

  • システムは異なるレジスタサイズを持ち、データとアドレッシングに異なる基本幅を使用できます。そのようなシステムにとって「ターゲットアーキテクチャビット」が何を意味するかさえわからないため、特定のABIを選択する必要がありますそのABIのための答えを知っています)。

  • システムは、古いnearfarhugeポインターなど、さまざまなポインターモデルをサポートする場合があります。その場合、コードがどのモードでコンパイルされているかを知る必要があります(そして、そのモードの答えを知っています)

  • システムは、前述のX32 ABI、または説明されている他の一般的な64ビットデータモデルのいずれかなど、さまざまなポインタサイズをサポートしている可能性があります here

最後に、あなたが興味のあるTに対して直接sizeof(T)を直接使用できるので、この仮定に明白な利点はありません。

整数とポインタの間で変換したい場合は、intptr_t。整数とポインタを同じ空間に格納したい場合は、unionを使用してください。

9
Useless

ターゲットアーキテクチャの「ビット」は、レジスタサイズについて述べています。例Intel 8051は8ビットで、8ビットレジスタで動作しますが、(外部)RAMおよび(外部)ROMは16ビット値でアクセスされます。

8
MamCieNaHita

正確さの場合、何も仮定できません。奇妙な状況をチェックして対処する準備をする必要があります。

一般的な経験則として、それは合理的デフォルト仮定

しかし、それは普遍的には真実ではありません。たとえば X32 ABI を参照してください。64ビットアーキテクチャで32ビットポインタを使用して、メモリとキャッシュフットプリントを少し節約しています。 AArch64上のILP32 ABIについても同様です。

したがって、メモリ使用量を推測するには、仮定を使用することができます。

5
Jesper Juhl

任意のタイプのポインタ(関数へのポインタを含む)の一般的なサイズがターゲットアーキテクチャビットと等しいと仮定するのは理にかなっていますか?

現在製造されているすべてのタイプのCPU(マイクロコントローラーを含む)を見ると、私はノーだと思います。

極端な反例は、2つの異なるポインタサイズが同じプログラムで使用されるアーキテクチャです。

x86、16ビット

MS-DOSおよび16ビットWindowsでは、「通常の」プログラムは16ビットと32ビットの両方のポインタを使用していました。

x86、32ビットセグメント

このメモリモデルを使用するあまり知られていないオペレーティングシステムはわずかしかありませんでした。

プログラムは通常、32ビットと48ビットの両方のポインターを使用しました。

STM8A

この最新の自動車用8ビットCPUは、16ビットと24ビットのポインターを使用します。もちろん、どちらも同じプログラムです。

AVR小さなシリーズ

RAMは8ビットポインターを使用してアドレス指定され、Flashは16ビットポインターを使用してアドレス指定されます。

(ただし、私の知る限り、AVR tinyはC++でプログラムすることはできません。)

5
Martin Rosenau

これは正しくありません。たとえば、DOSポインター(16ビット)が遠い(seg + ofs)場合があります。

ただし、通常のターゲット(Windows、OSX、Linux、Android、iOS)の場合は正しいです。これらはすべて、ページングに依存するフラットプログラミングモデルを使用しているためです。

理論的には、x64の場合、下位32ビットのみを使用するシステムも使用できます。例は、LARGEADDRESSAWAREなしでリンクされたWindows実行可能ファイルです。ただし、これはx64に切り替えるときにプログラマがバグを回避するのに役立ちます。ポインタは32ビットに切り捨てられますが、64ビットのままです。

X64オペレーティングシステムでは、フラットモードのみが有効であるため、この仮定は常に当てはまります。 CPUのロングモードでは、GDTエントリが64ビットフラットになります。

また、x32 ABIについても触れていますが、これは同じページングテクノロジーに基づいているため、すべてのポインターが下の4GBにマップされるように強制します。ただし、これはWindowsと同じ理論に基づいている必要があります。 x64では、フラットモードしか使用できません。

32ビットプロテクトモードでは、最大48ビットのポインタを使用できます。 (セグメント化モード)。コールゲートを持つこともできます。ただし、そのモードを使用するオペレーティングシステムはありません。

4

歴史的に、マイクロコンピュータとマイクロコントローラでは、CPUが十分なメモリをアドレス指定でき、しかもトランジスタバジェット内に収まるように、ポインタは汎用レジスタよりも幅が広いことが多かった。ほとんどの8ビットCPU(8080、Z80、6502など)には16ビットアドレスがありました。

今日では、アプリが数ギガバイトのデータを必要としないため、ミスマッチが発生する可能性が高くなっています。そのため、ポインターごとに4バイトのメモリを節約する方が有利です。

CとC++の両方で、size_tuintptr_toff_tの各タイプが個別に提供され、オブジェクトの最大サイズを表します(メモリモデルがフラットでない場合、ポインターのサイズよりも小さい場合があります)。 、ポインタを保持するのに十分な幅の整数型、およびファイルオフセット(メモリ内で許可されている最大のオブジェクトよりも広い場合が多い)。 size_t(符号なし)またはptrdiff_t(符号付き)は、ネイティブのWordサイズを取得する最もポータブルな方法です。さらに、POSIXはシステムコンパイラにlongがこれらのいずれかを保持できることを意味するいくつかのフラグがあることを保証しますが、常にそうであるとは限りません。

2
Davislor

一般に、ポインターのサイズは、16ビットシステムでは2、24ビットシステムでは3、32ビットシステムでは4、64ビットシステムでは8になります。 [〜#〜] abi [〜#〜] およびCの実装に依存します。 AMDには ロングとレガシー モードがあり、 AMD64とIntel64のアセンブリ言語の違い プログラマーがいますが、これらは高水準言語では非表示になっています。

C/C++コードに関する問題は、プログラミングの慣習が不十分で、コンパイラの警告を無視していることが原因である可能性があります。 「 C++コードの64ビットプラットフォームへの移植に関する20の問題 」を参照してください。

参照:「 ポインタはさまざまなサイズにすることができますか? "および LRiOの答え

...特定の物理マシンではなく、C++とその準拠した実装について質問しています。 それを証明するために標準全体を引用する必要がありますが、単純な事実は、sizeof(Tの結果を保証しないということです*)任意のTの場合、および(当然の結果として)すべてのT1およびT2のsizeof(T1 *)== sizeof(T2 *)であることは保証されません)。

注:Whereis answered by JeremyP 、C99セクション6.3.2.3、サブセクション8:

あるタイプの関数へのポインターは、別のタイプの関数へのポインターに変換され、再び元に戻されます。結果は、元のポインタと同じになります。変換されたポインターを使用して、ポイントされた型と互換性のない型の関数を呼び出す場合、動作は未定義です。

GCCでは、組み込み関数を使用することにより、誤った仮定を回避できます: " オブジェクトサイズチェック組み込み関数 ":

組み込み関数:size_t __builtin_object_size(const void * ptr、int型)

ptrポインターが指すオブジェクトの末尾からptrまでの一定のバイト数を返す組み込み構成です(コンパイル時に既知の場合)。動的に割り当てられたオブジェクトのサイズを決定するために、関数は、alloc_size属性で宣言されるストレージを取得するために呼び出される割り当て関数に依存します(共通関数属性を参照)。 __builtin_object_sizeは、副作用について引数を評価することはありません。それらに何らかの副作用がある場合、タイプ0または1の場合は(size_t)-1を、タイプ2または3の場合は(size_t)0を返します。複数のオブジェクトが存在する場合、ptrがポイントでき、それらはすべてコンパイル時に既知です。 、返される数は、タイプ&2が0の場合、それらのオブジェクトの残りのバイト数の最大値であり、ゼロ以外の場合は最小値です。コンパイル時にptrが指すオブジェクトを判別できない場合、__ builtin_object_sizeはタイプ0または1の場合は(size_t)-1を、タイプ2または3の場合は(size_t)0を返す必要があります。

0
Rob