2016-08-12 9 views
1

このコード考えてみましょう:機能sscanf関数は、変数に割り当てる必要がありそうでない場合は奇妙な行動

#define TRANSLATOR_requestElectricityMeterWrite() do{addr = word_getAddress(); value = word_getValue(); }while(0) 

uint16_t value; 
uint8_t addr; 

bool dispatcher(void) 
{ 
    TRANSLATOR_requestElectricityMeterWrite(); 
    return true; 
} // AFTER this point (during debug) program goes to default handler 

int main(void) 
{ 
    if(dispatcher()) 
     continue; 
     . . . . 
     . . . . 
} 

uint16_t word_getValue(void) 
{ 
    uint16_t value; 
    sscanf("ABCD", "%4x", (unsigned int *)&value); 
    return value; 
} 

uint8_t word_getAddress(void) 
{ 
    uint8_t address; 
    sscanf("00", "%2x", (unsigned int *)&address); 
     ; 
    return address; 
} 

上記のコードが実行されると、if原因プログラム内の文がクラッシュする(いくつかのデフォルトハンドラに行くを)。

しかし、ときに私はこれまで2(word_getValueとword_ getAddres)の機能を変更します。

uint16_t word_getValue(void) 
{ 
    uint16_t value; 
    int i = 0;i++; 
    i = sscanf(WORD_getValueString(), "%4x", (unsigned int *)(&value)); 
    return value; 
} 

uint8_t word_getAddress(void) 
{ 
    uint8_t address; 
    int i = 0;i++; 
    i = sscanf(WORD_getNameString(), "%2x", (unsigned int *)(&address)); 
    return address; 
} 

それは動作します。ダミーのiがその問題を解決してくれると思われます。しかし、なぜそれは他の方法で動作しないのですか?

GNU ARM v4.8.3ツールチェーン

+0

'WORD_getValueString()'と 'WORD_getNameString()'はどこで定義されていますか?あなたのコードの別のバージョンを私たちに示しているようです。 –

+0

別のファイルです。しかし、彼らの宣言は含まれています。ビルド時に警告もエラーも報告されませんでした。 – Hairi

+0

私は[最小限の完全で、確認可能なコード]を投稿することを提案します(http://stackoverflow.com/help/mcve) –

答えて

3

どちらの関数も未定義の動作を呼び出すため、何かが起こる可能性があります。追加のローカル変数を追加すると、宛先変数の位置が変更され、誤ったサイズの影響が隠されます。

sscanf("ABCD", "%4x", (unsigned int *)&value); 

sscanfのみ2バイトを有する可変value、にsizeof(unsigned int)バイト(おそらく4)を格納します。

sscanf(WORD_getNameString(), "%2x", (unsigned int *)(&address)); 

は1バイトのみを持つ変数address、にsizeof(unsigned int)バイトを格納します。

この問題を解決する最も簡単な方法は、unsigned intに解析し、個別に目的地に解析された値を格納し、または単に値を返すことです:

uint16_t word_getValue(void) { 
    unsigned int value; 
    if (sscanf(WORD_getValueString(), "%4x", &value) == 1) 
     return value; 
    // could not parse a value, return some default value or error code 
    return 0; 
} 

uint8_t word_getAddress(void) { 
    unsigned int address; 
    if (sscanf(WORD_getNameString(), "%2x", &address) == 1) 
     return address; 
    // could not parse a value, return some default value or error code 
    return 0; 
} 

また、解析された値かどうかを確認したい場合があります宛先タイプの範囲内ですが、解析結果をそれぞれ4桁と2桁の16進数に制限するため、オーバーフローが発生しません。

+0

戻り値の型 'uint8_t'と' uint16_t'は明示的に戻り値をキャストしますか?今私は 'unsigned int'を返すので、私のプラットフォームでは' uint32_t'です。 – Hairi

+1

これらの関数によって返された 'unsigned int'値は' uint8_t'と 'uint16_t'の戻り型にそれぞれ暗黙的に変換されます。変換は完全に定義され、値は切り捨てられ、 'sscanf'によってどのように計算されるのかを示すと、戻り値の型の範囲内です。 – chqrlie

+0

偉大なので、あなたが提供する例は完璧な仕事をしています。私はデバッグしているボードの動作に非常に不満を持っていたことを認めなければなりません。これは、特にメモリが関与しているとき、私とC言語の危険性の新しい初心者のための良い例です。 10x :) @chqrlie – Hairi

1

%xフォーマットはunsigned引数(それはあなたのプラットフォーム上でuint32_tだと仮定)が必要です。 uint16_tまたはuint8_tに合格すると、メモリが壊れる可能性があります。あなたのケースでは、それはスタックを破損し、リターンアドレスを上書きします。 uint16_tの場合は%4hxuint8_tの場合は%2hhxをお試しください。

+1

'%4hx'は、宛先が' unsigned short'であるとみなします。たぶんそうかもしれませんが、 'uint16_t'が' unsigned short'と同じ型かどうかわからないので、技術的に危険です。 – chqrlie

関連する問題