2011年8月29日 星期一

alignment in the C

在建立struct時,有時候裡面的成員的排序很重要。
排序不同,會造成sizeof整個struct的值不同。

例如以下的例子:
struct aaaaa
{
  char a;
  int b;
  char c;
};

-byte數為12-

struct aaaaa
{
  char a;
  char c;
  int b;
};

-byte數為8-

========== START ==========

struct 的 alignment 有兩個, 首先是個別成員的 alignment, 然後是整個 struct 的 alignment.

首先, 每一個類型都會有它自己的 alignment requirement. 每個物件在記憶體裏的相對位置是:
  Offset % alignment-requirement == 0

假定在你的編譯器上, char 的 alignment requirement 是 1, int 的 alignment requirement 是 4.

在第一個 struct 裏, a 是在 offset 0 的位置, 而 b 則須在 offset 4 的位置. a 跟 b 之間會有 3 個 byte 的空格.

個別成員的 alignment 完成後就要做整個 struct 的 alignment. 這個關係到最後一個成員後面要加多少個 padding. 需要這個的原因是當你以 struct 來定義一個陣列的時候, 每一個 struct 元素都需要 align. 這時就要以到 struct 裡面最大的成員來做決定.

所以第一個 struct 最後一個成員 c 後面需要 3 個 bytes 的 padding. 這樣在陣列的架構下, 下一個元素的 b 也會 align:

    +=======+
  0 |a|x|x|x|
    |-------|
  4 |b      | 
    |-------|
  8 |c|x|x|x|
    +=======+
 12 |a|x|x|x|
    |-------|
 16 |b      |
    |-------|
 20 |c|x|x|x|
    +=======+
    |       |

第二個 struct 的 padding 結構:

    +=======+
  0 |a|c|x|x|
    |-------|
  4 |b      |
    +=======+
  8 |a|c|x|x|
    |-------|
 12 |b      |
    +=======+

而要解決此問題,最好在每一個struct裡面都放相同alignment的變數,
或是使用#pragma pack ()
A1:
#pragma pack(push, 1)
struct aaaaa
{
  char a;
  char c;
  int b;
};
#pragma pack(pop)

其中 "push" 的意思是先將原來的設定(可能是 4)暫存起來;而 "pop" 則是恢復原來的設定。

A2:
#pragma pack(1)
struct aaaaa
{
  char a;
  char c;
  int b;
};
#pragma pack(1)

意思是說,強制把alignment設定為1 byte

參考資料:
Visual C++ 的 Struct Memeber Alignment 問題與解決方法
C/C++ struct 的 alignment

Bit-field of struct

Bit-field 是一種省空間的特殊 data member, 可以使用特定幾個 bit 來放 data.

#include <stdio.h>

struct Data_Detail
{
  unsigned short s1 : 1;
  unsigned short s2 : 3;
  unsigned short s3 : 4;
  unsigned short s4 : 8; // 剛好塞滿一個 16 bit 之 unsigned short
};

typedef struct _Data_View {
  char v1 : 1;
  char v2 : 1;
  char Reserve : 6; // 保留6 bit,當以後需要可以使用
} Data_View;

int
main (
  )
{

{
  Data_View X;
  X.v1 = 0;
  X.v2 = 1;
  printf ("X.v1 is %d\n", X.v1);
  printf ("X.v2 is %d\n", X.v2);
}

{
  struct Data_Detail Value;
  Value.s1 = 1;
  Value.s2 = 2;
  Value.s3 = 3;
  Value.s4 = 4;
  printf ("Value.s1 = %d\n", Value.s1);
  printf ("Value.s2 = %d\n", Value.s2);
  printf ("Value.s3 = %d\n", Value.s3);
  printf ("Value.s4 = %d\n", Value.s4);
}

  return 0;
}


建議同一個struct裡面的Bit-field是同一型別,避免因為不同型別而導致錯亂。

若不同的type放在同一個struct裡面,我猜想每一個同型別所需要的空間會算在一起。
例如:
char v1 : 1與char v2 : 1與char Reserve : 6所需要的空間一共是8bit,剛好就是一個char。
所以,用sizeof (Data_View)會是1 (byte)


若在Data_View的結構裡面,加入一個unsigned short s4 : 1
其中unsigned short 是 2 bytes
用sizeof (Data_View)的值會是4,我猜原因是char與unsigned short是分開算的,並且以其中一個最大的為單位,
因為最大是unsigned short是2 bytes,所以,一共是4bytes。

不過,我想這個結果可能跟不同的Compiler不同,而結果有所不同。

參考資料:
Union and Bit-field

2011年8月25日 星期四

在win7下建立一個external partion

在WIN7下,並沒有辦法直接透過Disk Management去分割出一個Extended Boot Record(延伸磁區)

但是,仍然可以透過WIN7內建的指令來建立Extended Boot Record,

1. 透過administrator去開啟cmd
2. > diskpart
3. DISKPART> list disk
4. DISKPART> select disk 0
5. DISKPART> create partition extend size = 40960
6. DISKPART> create partition logical size = 40960

把id改為84
7. DISKPART> list disk
8. DISKPART> select volume = x
9. DISKPART> set id=84 override


其它:
delete partition [noerr] [override]
delete partition 指令可以刪除目前設為焦點的磁碟分割。
Diskpart 不允許刪除目前的系統、開機或分頁磁碟區。若要刪除 ESP、MSR 或已知的 OEM 磁碟分割,您必須指定 override 參數。

參考資料:
[Tutorial] 如何在 Windows Vista / 7 下分割延伸磁區 (Extended Partition) ?
Diskpart 指令行公用程式的說明

2011年8月23日 星期二

讓程式碼的function內的component比較容易被抽換

讓程式碼的function內的component比較容易被抽換


當一個函式很大時,裡面包含許多component,為了程式碼容易看,
在程式中可以透過大括號把一個函式分成許多部分~

如以下的例子:

#include <stdio.h>

int
main (
)
{
  int y = 2;

  printf ("Hello, Y value is %d\n", y);

#ifdef FOMLOAD
{
  int x = 1;
  printf ("X value is %d\n", x);
  printf ("Y value is %d\n", y);
}
#endif //#ifndef FOMLOAD

//  printf ("X value is %d\n", x); // variable x does not exist in this scope

  return 0;
}


如此一來,被FOMLOAD包起來的程式碼,就可以依照FOMLOAD在Build Time的時候就決定要不要被執行~
這樣一來程式碼比較容易讀,在抽取的Component時,也比較容易。

另外,在上面的例子中,在大括號被宣告的int x變數,其能被存取的也只有在大括號中。

如此一來,更可以節省一些記憶體的空間。

另外,為了讓程式設計師比對有使用這一個方式跟沒有的差別。
以下是沒有使用這一個大括號的設計方式的程式碼;

#include <stdio.h>

int
main (
)
{
#ifndef FOMLOAD
  int x = 1;
#endif //#ifndef FOMLOAD
  int y = 2;

  printf ("Hello, Y value is %d\n", y);

#ifdef FOMLOAD
  printf ("X value is %d\n", x);
  printf ("Y value is %d\n", y);
#endif //#ifndef FOMLOAD

//  printf ("X value is %d\n", x); // variable x does not exist in this scope

  return 0;
}


如此一來,被FOMLOAD被分散在兩個地方,目前這一個例子的程式碼比較小,可能看不出差異,
當程式碼很大時,這兩個部分可能會被分散在好幾行之後(因為,宣告變數一定要在function的開頭)
這樣程式在可讀性上,就比較差。

Makefile
# Program, flags, etc.
CC = cl
CCFLAGS = /c
OBJ = WithCurelyBrace.obj
TARGET = WithCurelyBrace.exe

!IFDEF NOTSHOW
FLAGS =
!ELSE
FLAGS = /DFOMLOAD
!ENDIF

everything: $(TARGET)

clean:
[tab]del $(OBJ) $(TARGET)

all: clean everything

.c.obj:
[tab]$(CC) $(CCFLAGS) $< /Fo$@ $(FLAGS)

$(TARGET): $(OBJ)
[tab]$(CC) $(OBJ) /Fe$@


指令如下:
nmake all NOTSHOW=1
不會顯示括號內的指令

nmake all
會顯示括號內的指令

Snagit截圖軟體

功能很強大的截圖軟體...