【C陷阱與缺陷】邊界計算與不對稱邊界

前言

本文與爲什麼C語言從0開始編號搭配實用更佳。
如有不足還請指正!


正文

如果一個數組有10個元素,那麼這個數組下表的允許取值範圍是什麼呢?

下面代碼1,這段代碼的運行結果是什麼?爲什麼?

#include <stdio.h>
// 代碼1
int main()
{
    int i = 0;
    int arr[10] = {0};
    
    for(i=0; i<12; i++)	// 注意這裏是 i<12
    {
        arr[i] = 0;
        printf("hey, girl!\n");
    }
    
    return 0;
}

結果出人意料。

這段代碼在Windows下DevC++下運行結果是:死循環。。。

來看看why:

#include <stdio.h>

int main()
{
	int i = 0;
	int arr[10] = {0};
	
	printf("&i = %p\n", &i);
	printf("&arr[0] = %p\n", &arr[0]);
	printf("&arr[10] = %p\n", &arr[10]);
	printf("&arr[11] = %p\n", &arr[11]);
	
	return 0;
 }

從輸出的結果可以得到iarr[]在內存中的樣子:

&arr[11] == &i,當循環中做arr[11] = 0;這步操作時,也把循環變量i的值改爲了0,這樣就陷入了一個死循環。

我們把int i = 0;這句話寫在數組int arr[10] = {0};這句的後面再試試:

可以看到,這次 i 的地址就在數組arr地址的下面了,這下就不是死循環的情況咯。

另外!對於不同平臺下代碼1的運行結果是不同的:

  1. Microsoft Visual Studio在debug下是死循環,但是在release下編譯運行程序就不是死循環,debug模式下沒有進行任何優化,在release模式下將變量在內存中進行了優化,不會出現死循環。

  2. 在MAC OS gcc下編譯執行代碼1,不會死循環,而且不論i是定義在數組上面還是數組下面,i的地址都是在數組首元素地址的前面,數組向後訪問,不會訪問到i。

    pic1

    pic2

    嘿嘿,如果i是負數的話,還是會改變到循環變量i的地址滴,下面這樣:

    #include <stdio.h>
    
    int main()
    {
        int i = 1234;
        int arr[10] = {0};
        
        printf("&i = %p\n", &i);
        printf("&arr[0] = %p\n", &arr[0]);
        
        for(i=0; i<12; i++)
        {
            arr[-i] = 1;
            printf("nice to meet you!\n");
        }
        
        return 0;
    }
    

不對稱邊界

C語言中數組的這種特別的設計正是其最大優勢所在。 ——《C陷阱與缺陷》

在編碼過程中,for語句循環控制變量採用半開半閉區間的寫法。 ——《高質量C++/C編程指南》的建議【4-5-1】

why?

這個其實是爲了增加代碼的可讀性,看下面這段代碼:

int main()
{
    // 1
    for(int i=16; i<=37; i++)
    {
        something1();
    }
    // 2
    for(int j=16; j<38; j++)
    {
        something2();
    }
    
    return 0;
}

問題:在第一個循環中something1()被調用了多少次,在第二個循環中something2()被調用多少次。

答案:37-16+1 = 22次,38-16 = 22。

顯然半開半閉區間比較直觀。

這種不對稱也許從數學上而言並不優美,但是它對於程序設計的簡化效果卻足以令人吃驚:

  1. 取值範圍的大小就是上界與下界的差。38-16的值是22,恰是不對稱邊界16和38之間所包括的元素數目。
  2. 如果取值範圍爲空,那麼上界等於下界。
  3. 即使取值範圍爲空,上界也永遠不可能小於下界。

像C語言這樣的數組下標從0開始的語言,不對稱邊界給程序設計帶來的便利尤爲明顯:這種數組的上界(即第一個“出界點”)恰是循環執行的次數。

按照這種不對稱邊界的慣例,有如下語句:

char* my_strcpy(char *str_dest, const char *str_src)
{
    assert(str_dest != NULL);
    assert(str_src != NULL);
    
    char *tmp = str_dest;
    
    while((*tmp++ = *str_src++))	// 這裏是重點
    {
        ;
    }
    
    return str_dest;
}

另外:在計算字符串長度時候,我們可以用這樣的上界指針減去字符串首元素地址的指針然後除每個元素大小。

string_length = (upp - downp)/sizeof(downp[0]);


參考書籍:

《高質量C++/C編程指南》、《C陷阱與缺陷》

相關文章:爲什麼C語言從0開始編號


完,如有不足還請指正!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章