【C言語】
デッドコード
~到達不能コードと冗長コードの2種類ある~

warning: ‘return’ will never be executed

[-Wunreachable-code-return]


■デッドコードは2種類ある

2大コーディングルールで微妙に違います。

CERT-Cルールでは
到達不能コード(unreachable code)の事。
実行される事が無い。

MISRA-Cルールでは
冗長コード(redundant code)の事。
実行する意味が無い。

この記事は、
C言語におけるデッドコードの種類について説明しています。


■到達不能コード(unreachable code)の例

➡break直後のreturn

#include <stdio.h>
typedef enum    {R,G,B} color;
char    *RGB(color x){
    switch(x){
    case R:
        return  "赤";
        break;
    case G:
        return  "緑";
        break;
    case B:
        break;
        return  "青";//バグ
    }
    return  "予期しない値";
}
int main(void){
    printf("%s\n",RGB(B));
}

breakとreturnの順番を間違えているので
returnには制御が来ません。


➡if文が矛盾しているので制御が来ない

(warning): Opposite inner ‘if’ condition leads to a dead code block.

警告:矛盾する逆条件はデッドコードになる[(warning)oppositeInnerCondition]

#include <stdio.h>
int f(int x)
{
    if(x != 0) {
        if(x == 0){//NG
            return  1;
        }
    }
    return  0;
}

4行目と5行目が矛盾するので
6行目がデッドコードブロックになります。


➡無条件breakでfor文更新に制御が来ない

warning: loop will run at most once

警告:ループは最大でも一度しか実行されない
[-Wunreachable-code-loop-increment]

#include    <stdio.h>
int main(void)
{
    for(int i = 0 ;  i < 10 ;  i++ ) {
        printf("%d\n",i);
        //回りたい?、止まりたい? デバッグの残骸? 
        break ;
    }
}

7行目にbreakがあるので、for文第三式 i++ が実行されません。

デバッグ文を書いたり消したりして
残骸として残った可能性がありますが、
保守者には break の意味がわかりません。


➡最初のcaseの上に色々書いても無駄

warning: statement will never be executed

警告:宣言時の代入文が実行されることはない
[-Wswitch-unreachable]

#include <stdio.h>
int main(int argc,char *argv[]){
    switch(argc){
        int i = 1234 ;  //変数宣言は有効だが実行はされない
        int j;          //変数宣言は有効
        j = 5678 ;      //実行はされない
        printf("デバッグ文も実行されない%d:%d\n",i,j);
        break;
    case    1:  
        printf("変数は未初期化 %d:%d\n",i,j);
        break;  
    case    2: 
        printf("変数は未初期化 %d:%d\n",i,j); 
        break  ;
    case    3:  
        printf("変数は未初期化 %d:%d\n",i,j);
        break  ;  
    }
}

case 前の変数宣言は可能ですが実行はされません
このため変数の初期化やデバッグ文は実行はされないので
注意して下さい。

参考:

DCL41-C. switch 文のなかでは最初の case 句より前で変数宣言しない

https://www.jpcert.or.jp/sc-rules/c-dcl41-c.html

■冗長コード(redundant code)の例

➡同じ比較を2回

(warning) Identical condition ‘x==1’, second condition is always false

警告:同じ比較をしているので2番目の比較は常に偽になる[(warning)identicalConditionAfterEarlyExit]

#include <stdio.h>
typedef enum    {R,G,B} color;
char    *RGB(color x){
    if(x == R){
        return  "赤";
    }
    if(x == R){
        return  "緑";
    }
    if(x == B){
        return  "青";
    }
    return  "予期しない値";
}
int main(void){
    printf("%s\n",RGB(G));
}

if(x == R)の比較を2回行っています。
1回目のif文が成立するとreturn するので、
2回目のif文が成立する事はありません。


➡両辺同じif(p->x!=0 && p->x!=0)

warning: self-comparison always evaluates to true

[-Wtautological-compare]
警告:両辺同じ物の比較は常に真になる

#include <stdio.h>
#include <stdbool.h>
typedef struct {
    int     Low;
    int     High;
} tag ;
bool same(tag data1, tag data2){
  return (data1.Low  == data2.Low) &&
         (data2.High == data2.High);
}
int main(void){
  tag   data1 = {0,123};
  tag   data2 = {0,456};
  if(same(data1,data2)){
    puts("同じ");
  }
}

論理演算子の両辺が同じなので
条件式に意味がありません。


➡then節とelse節が同じ

warning: this condition has identical branches

警告:then節とelse節が同じ
[-Wduplicated-branches]

#include <stdio.h>
char *samediff(int x,int y){
    static char  ans[128];
    if(x == y){
        sprintf(ans,"%dと%dは同じ",x,y);
        return  ans;
    } else {
        sprintf(ans,"%dと%dは同じ",x,y);//バグ
        return  ans;
    }
}
int main(void){
    printf("%s\n",samediff(1,2));
}

then節とelse節が同じなのでif文の意味がありません。
この問題はthen節をコピーしてelse 節を作り、
else節の変更を忘れた時等に発生します。


➡同一変数に上書き代入

(style): Variable ‘ret’ is reassigned a value before the old one has been used.

(スタイル):変数’ret’は古い値が使用される前に再設定された。
[(style)redundantAssignment]

extern int Sin(void),Cos(void);
int f1(void){
    int ret = 0; 
    ret = Sin() ;//警告されない
    return  ret ;
}
int f2(void) {
    int  ret;
    ret = 0 ; 
    ret = Sin() ;//警告されない
    return  ret ;
}
int f3(void) {
    int  ret ;
    ret = Sin() ; 
    ret = Cos() ;//★警告される
    return  ret ;
}

この cppcheck の警告は3行目、9行目のような
とりあえず0で初期化した変数に再設定しても警告は出ません。


16行目のようにretを上書きして何のためにSin()関数を呼んだのか不明なものだけ警告してくれます。


➡*p++の丸括弧が足りない

warning: value computed is not used
warning: expression result unused

警告:計算された値は使用されません
[-Wunused-value]

#include    <stdio.h>
void    increment(int *p)
{
    *p++ ;//ダメ:デッドコード
}
int main(void)
{
    int     x = 1234 ;
    printf("前:%d\n",x);
    increment(&x);
    printf("後:%d\n",x);
}

*p++ の * がデッドコードです。
このincrement関数を呼んでも
ポインタpの指す先は加算されません。

➡(*p)++に修正


➡*p++の*が多い

#include    <stdio.h>
int my_strlen(char *p){
    int len = 0 ; 
    while(*p != '\0'){
        *p++ ;//ダメ:デッドコード
        len++ ;
    }
    return  len;
}
int main(void){
    printf("%d\n",my_strlen("abcd"));
}

4行目の*p != ‘\0’につられて
5行目も*p++ と書いてしまう場合が多いですが
5行目の*p++ の*がデッドコードです。


➡p++に修正

参考:

C-FAQ 4.3: *p++はpを増分するか。それともpが指すものを増分するのか。

http://www.kouno.jp/home/c_faq/c4.html#3

➡return x++ の後置演算子が冗長

warning: Value stored to ‘x’ is never read

警告:’x’に格納された値が読み出されない
[clang-analyzer-deadcode.DeadStores]

#include    <stdio.h>
int increment(int x)
{
    return  x++;    //NG:意味なし++
}
int main(void)
{
    printf("%d\n",increment(1234)) ;//1234と表示される
}

自動変数 x を return 時に後置演算で ++ しても、
もはや誰も参照できないので 後置演算++ に意味がありません。
++ した値を return したいのであれば
2行に分けるか、
前置演算子を使いましょう

➡修正案1:2行に分け

➡修正案2:前置演算子を使う


参考:

MSC07-C. デッドコードを検出して削除する