원문 출처 : http://soen.kr/lecture/ccpp/cpp2/18-1-5.htm



첫 번째로 구조체가 시작될 번지(base)를 고를 때 가급적이면 16바이트 경계에서 시작하도록 한다. 왜냐하면 최신 CPU들은 속도 증가를 위해 캐시를 사용하는데 캐시의 단위가 16바이트로 되어 있기 때문이다. 캐시 크기의 배수 위치에 구조체를 배치하면 이 구조체를 자주 액세스할 때 캐시 용량을 덜 차지하면서도 빠르게 액세스할 수 있다. 만약 16바이트 경계의 양쪽에 걸치게 되면 캐시도 많이 차지할 뿐더러 액세스 속도도 느려질 것이다.

두 번째로 구조체의 멤버를 배치할 때 멤버의 오프셋도 액세스하기 유리한 위치로 조정한다. 별다른 지정이 없으면 멤버의 크기에 따라 자연스러운 경계 위치에 맞추도록 되어 있는데 예를 들어 int는 4바이트, double은 8바이트 경계에 맞춘다. 그래서 위 예제의 경우 c가 1바이트를 차지하고 난 후 d는 다음 8바이트 경계에 배치되므로 c와 d사이에 7바이트는 버려지고 사용되지 않는다. 이렇게 사용되지 않고 버려지는 공간을 패딩(Padding) 이라고 한다.



컴파일러는 CPU가 메모리를 최대한 빠른 속도로 액세스할 수 있도록 구조체의 베이스와 멤버의 오프셋을 조정해서 배치하는데 이를 구조체의 정렬(alignment)이라고 한다. 자료를 크기순으로 나열하는 정렬(Sort)과는 번역만 같으며 뜻은 다르다. 개발자들은 일반적으로 구조체의 정렬 방식에 대해 몰라도 별 문제가 없다. 왜냐하면 변수가 어떤 메모리에 배치되는가는 원칙적으로 컴파일러 마음이며 개발자는 변수명으로 그 번지의 내용을 읽고 쓰기 때문이다. 또한 멤버의 오프셋이 어떻게 설정되든간에 코드에서는 st1.c, st1.d 연산문으로 멤버를 액세스할 수 있으며 . 연산자는 컴파일러가 정한 오프셋을 정확하게 찾아 준다.

구조체의 정렬 기능에 의해 액세스 속도는 빨라지지만 효율을 위해 버려지는 메모리가 있다는 점이 다소 안타까워 보일 것이다. 그러나 위의 tag_st1은 이런 효과를 극대화해서 보여주기 위해 1바이트 멤버 다음에 8바이트 멤버를 의도적으로 배치했을 뿐이지 현실적으로 구조체의 멤버들은 대부분 int, unsigned, char [ ] 등이기 때문에 걱정하는 것만큼 메모리가 낭비되지는 않는다.

(출처 : http://msdn.microsoft.com/ko-kr/library/vstudio/ttcz0bys(v=vs.110).aspx)


C4996 error 발생시.


#pragma warning(disable : 4996)



단 실제 개발에서는 컴파일러에서 하라는대로 변경할 것.





원문 : http://www.soen.kr/lecture/ccpp/cpp2/18-3-3.htm


#pragma warning(경고제어문:경고번호)

 

경고 제어문의 종류는 다음과 같으며 제어문 다음에 : 과 함께 대상 경고의 번호를 적는다. 경고 번호는 공백으로 구분하여 여러 개를 나열할 수 있으면 경고 제어문도 콜론으로 구분하여 여러 개를 나열할 수 있다.

 

제어문

설명

once:번호

반복되는 경고를  번만 출력한다.

default:번호

원래 설정대로 되돌린다.

disable:번호

경고를 출력하지 않는다.

error:번호

경고를 에러로 처리한다.

레벨:번호

경고의 레벨(1~4) 변경한다.

push[,n]

모든 경고의 레벨을 저장한다. n 있을 경우 저장과 동시에 전역 경고레벨을 n으로 변경한다.

pop

스택에 마지막으로 저장된 경고 레벨을 복원한다.

 

소스의 어느 위치에나 다음 명령을 삽입하면 이후부터 컴파일러가 경고를 통제하는 방법이 바뀐다.

 

#pragma warning (disable:4101)            // 경고를 무시하도록 한다.

#pragma warning (once:4101)          // 4101경고를 한 번만 출력한다.

#pragma warning (error:4700)               // 경고 대신 에러를 출력한다.

#pragma warning (3:4706)                // 4706번 경고를 레벨 3으로 올린다.

 


ICS 161: Design and Analysis of Algorithms
Lecture notes for February 15, 1996


Breadth first search and depth first search

Traversal of graphs and digraphs

To traverse means to visit the vertices in some systematic order. You should be familiar with various traversal methods for trees:
preorder: visit each node before its children.
postorder: visit each node after its children.
inorder (for binary trees only): visit left subtree, node, right subtree.

We also saw another kind of traversal, topological ordering, when I talked about shortest paths.

Today, we'll see two other traversals: breadth first search (BFS) and depth first search (DFS). Both of these construct spanning trees with certain properties useful in other graph algorithms. We'll start by describing them in undirected graphs, but they are both also very useful for directed graphs.

Breadth First Search

This can be throught of as being like Dijkstra's algorithm for shortest paths, but with every edge having the same length. However it is a lot simpler and doesn't need any data structures. We just keep a tree (the breadth first search tree), a list of nodes to be added to the tree, and markings (Boolean variables) on the vertices to tell whether they are in the tree or list.

breadth first search:

    unmark all vertices
    choose some starting vertex x
    mark x
    list L = x
    tree T = x
    while L nonempty
    choose some vertex v from front of list
    visit v
    for each unmarked neighbor w
        mark w
        add it to end of list
        add edge vw to T
It's very important that you remove vertices from the other end of the list than the one you add them to, so that the list acts as a queue (fifo storage) rather than a stack (lifo). The "visit v" step would be filled out later depending on what you are using BFS for, just like the tree traversals usually involve doing something at each vertex that is not specified as part of the basic algorithm. If a vertex has several unmarked neighbors, it would be equally correct to visit them in any order. Probably the easiest method to implement would be simply to visit them in the order the adjacency list for v is stored in.

Let's prove some basic facts about this algorithm. First, each vertex is clearly marked at most once, added to the list at most once (since that happens only when it's marked), and therefore removed from the list at most once. Since the time to process a vertex is proportional to the length of its adjacency list, the total time for the whole algorithm is O(m).

Next, let's look at the tree T constructed by the algorithm. Why is it a tree? If you think of each edge vw as pointing "upward" from w to v, then each edge points from a vertex visited later to one visited earlier. Following successive edges upwards can only get stopped at x (which has no edge going upward from it) so every vertex in T has a path to x. This means that T is at least a connected subgraph of G. Now let's prove that it's a tree. A tree is just a connected and acyclic graph, so we need only to show that T has no cycles. In any cycle, no matter how you orient the edges so that one direction is "upward" and the other "downward", there is always a "bottom" vertex having two upward edges out of it. But in T, each vertex has at most one upward edge, so T can have no cycles. Therefore T really is a tree. It is known as a breadth first search tree.

We also want to know that T is a spanning tree, i.e. that if the graph is connected (every vertex has some path to the root x) then every vertex will occur somewhere in T. We can prove this by induction on the length of the shortest path to x. If v has a path of length k, starting v-w-...-x, then w has a path of length k-1, and by induction would be included in T. But then when we visited w we would have seen edge vw, and if v were not already in the tree it would have been added.

Breadth first traversal of G corresponds to some kind of tree traversal on T. But it isn't preorder, postorder, or even inorder traversal. Instead, the traversal goes a level at a time, left to right within a level (where a level is defined simply in terms of distance from the root of the tree). For instance, the following tree is drawn with vertices numbered in an order that might be followed by breadth first search:

        1
      / | \
    2   3   4
      /   \     |
    5       6   7
    |     / | \
    8    9 10 11
The proof that vertices are in this order by breadth first search goes by induction on the level number. By the induction hypothesis, BFS lists all vertices at level k-1 before those at level k. Therefore it will place into L all vertices at level k before all those of level k+1, and therefore so list those of level k before those of level k+1. (This really is a proof even though it sounds like circular reasoning.)

Breadth first search trees have a nice property: Every edge of G can be classified into one of three groups. Some edges are in T themselves. Some connect two vertices at the same level of T. And the remaining ones connect two vertices on two adjacent levels. It is not possible for an edge to skip a level.

Therefore, the breadth first search tree really is a shortest path tree starting from its root. Every vertex has a path to the root, with path length equal to its level (just follow the tree itself), and no path can skip a level so this really is a shortest path.

Breadth first search has several uses in other graph algorithms, but most are too complicated to explain in detail here. One is as part of an algorithm for matching, which is a problem in which you want to pair up the n vertices of a graph by n/2 edges. If you have a partial matching, pairing up only some of the vertices, you can extend it by finding an alternating path connecting two unmatched vertices; this is a path in which every other edge is part of the partial matching. If you remove those edges in the path from the matching, and add the other path edges back into the matching, you get a matching with one more edge. Alternating paths can be found using a version of breadth first search.

A second use of breadth first search arises in certain pattern matching problems. For instance, if you're looking for a small subgraph such as a triangle as part of a larger graph, you know that every vertex in the triangle has to be connected by an edge to every other vertex. Since no edge can skip levels in the BFS tree, you can divide the problem into subproblems, in which you look for the triangle in pairs of adjacent levels of the tree. This sort of problem, in which you look for a small graph as part of a larger one, is known as subgraph isomorphism. In a recent paper, I used this idea to solve many similar pattern-matching problems in linear time.

Depth first search

Depth first search is another way of traversing graphs, which is closely related to preorder traversal of a tree. Recall that preorder traversal simply visits each node before its children. It is most easy to program as a recursive routine:
    preorder(node v)
    {
    visit(v);
    for each child w of v
        preorder(w);
    }
To turn this into a graph traversal algorithm, we basically replace "child" by "neighbor". But to prevent infinite loops, we only want to visit each vertex once. Just like in BFS we can use marks to keep track of the vertices that have already been visited, and not visit them again. Also, just like in BFS, we can use this search to build a spanning tree with certain useful properties.
    dfs(vertex v)
    {
    visit(v);
    for each neighbor w of v
        if w is unvisited
        {
        dfs(w);
        add edge vw to tree T
        }
    }
The overall depth first search algorithm then simply initializes a set of markers so we can tell which vertices are visited, chooses a starting vertex x, initializes tree T to x, and calls dfs(x). Just like in breadth first search, if a vertex has several neighbors it would be equally correct to go through them in any order. I didn't simply say "for each unvisited neighbor of v" because it is very important to delay the test for whether a vertex is visited until the recursive calls for previous neighbors are finished.

The proof that this produces a spanning tree (the depth first search tree) is essentially the same as that for BFS, so I won't repeat it. However while the BFS tree is typically "short and bushy", the DFS tree is typically "long and stringy".

Just like we did for BFS, we can use DFS to classify the edges of G into types. Either an edge vw is in the DFS tree itself, v is an ancestor of w, or w is an ancestor of v. (These last two cases should be thought of as a single type, since they only differ by what order we look at the vertices in.) What this means is that if v and w are in different subtrees of v, we can't have an edge from v to w. This is because if such an edge existed and (say) v were visited first, then the only way we would avoid adding vw to the DFS tree would be if w were visited during one of the recursive calls from v, but then v would be an ancestor of w.

As an example of why this property might be useful, let's prove the following fact: in any graph G, either G has some path of length at least k. or G has O(kn) edges.

Proof: look at the longest path in the DFS tree. If it has length at least k, we're done. Otherwise, since each edge connects an ancestor and a descendant, we can bound the number of edges by counting the total number of ancestors of each descendant, but if the longest path is shorter than k, each descendant has at most k-1 ancestors. So there can be at most (k-1)n edges.

This fact can be used as part of an algorithm for finding long paths in G, another subgraph isomorphism problem closely related to the traveling salesman problem. If k is a small constant (like say 5) you can find paths of length k in linear time (measured as a function of n). But measured as a function of k, the time is exponential, which isn't surprising because this problem is closely related to the traveling salesman problem. For more on this particular problem, see Michael R. Fellows and Michael A. Langston, "On search, decision and the efficiency of polynomial-time algorithms", 21st ACM Symp. Theory of Computing, 1989, pp. 501-512.

Relation between BFS and DFS

It may not be clear from the pseudo-code above, but BFS and DFS are very closely related to each other. (In fact in class I tried to describe a search in which I modified the "add to end of list" line in the BFS pseudocode to "add to start of list" but the resulting traversal algorithm was not the same as DFS.)

With a little care it is possible to make BFS and DFS look almost the same as each other (as similar as, say, Prim's and Dijkstra's algorithms are to each other):

    bfs(G)
    {
    list L = empty
    tree T = empty
    choose a starting vertex x
    search(x)
    while(L nonempty)
        remove edge (v,w) from start of L
        if w not yet visited
        {
        add (v,w) to T
        search(w)
        }
    }

    dfs(G)
    {
    list L = empty
    tree T = empty
    choose a starting vertex x
    search(x)
    while(L nonempty)
        remove edge (v,w) from end of L
        if w not yet visited
        {
        add (v,w) to T
        search(w)
        }
    }

    search(vertex v)
    {
    visit(v);
    for each edge (v,w)
        add edge (v,w) to end of L
    }
Both of these search algorithms now keep a list of edges to explore; the only difference between the two is that, while both algorithms adds items to the end of L, BFS removes them from the beginning, which results in maintaining the list as a queue, while DFS removes them from the end, maintaining the list as a stack.

BFS and DFS in directed graphs

Although we have discussed them for undirected graphs, the same search routines work essentially unmodified for directed graphs. The only difference is that when exploring a vertex v, we only want to look at edges (v,w) going out of v; we ignore the other edges coming into v.

For directed graphs, too, we can prove nice properties of the BFS and DFS tree that help to classify the edges of the graph. For BFS in directed graphs, each edge of the graph either connects two vertices at the same level, goes down exactly one level, or goes up any number of levels. For DFS, each edge either connects an ancestor to a descendant, a descendant to an ancestor, or one node to a node in a previously visited subtree. It is not possible to get "forward edges" connecting a node to a subtree visited later than that node. We'll use this property next time to test if a directed graph is strongly connected (every vertex can reach every other one).


ICS 161 -- Dept. Information & Computer Science -- UC Irvine
Last update: 07 May 2004, 15:27:14 PDT



원본 출처 : http://www.winapi.co.kr/clec/cpp1/6-4-4.htm
ASCII
정보 교환용 미국 표준 부호. 아스키는 문자, 숫자, 구두점 및 기타 일부 특수 문자에 수치(numeric value)를 부여하는 부호 체계로서, 1962년 미국표준협회(ANSI)가 제정했다. 1967년 국제표준화기구(ISO)가 ISO 646으로 제정한 7비트 정보 교환용 부호도 이를 준거한 것이다. 각 문자에 사용되는 수치를 표준화함으로써 아스키는 컴퓨터 및 프로그램이 정보를 교환할 수 있게 한다. 아스키는 모두 256개의 부호를 각각 128개로 이루어진 2개의 집합, 즉 표준 집합과 확장 집합으로 구분하여 제공한다. 표준 아스키 부호 집합에서는 각 부호에 7비트를 사용하여 0부터 127(16진수 00H부터 7FH)까지 128개 문자 부호를 제공한다. 확장 아스키 부호 집합에서는 각 부호에 8비트를 사용하여 128부터 255(16진수 80H부터 FFH)까지 128개 문자 부호를 추가로 제공한다. 표준 아스키 부호 집합 128개 중에서 0부터 32까지의 부호는 후진 문자(BS), 나르개 되돌림 문자, 탭 문자와 같은 비인쇄 문자, 즉 컴퓨터와 컴퓨터 간의 정보 전송 또는 컴퓨터와 인쇄기 간의 정보 전송을 제어하는 데 사용되는 제어 문자에 할당되어 있다. 나머지 96개 부호는 로마자의 대문자와 소문자, 0부터 9까지의 숫자 및 널리 사용되는 구두점 등 특수 기호에 할당되어 있다. 그리고 128부터 255까지의 확장 아스키 부호 집합은 컴퓨터 생산자와 소프트웨어 개발자가 여러 가지 다양한 문자에 할당할 수 있도록 하고 있다. 이렇게 할당된 확장 부호는 표준 아스키 부호와 같이 서로 다른 프로그램이나 컴퓨터 사이에서 교환되지 못한다. 예를 들면, IBM은 보통 IBM 확장 문자 집합(IBM Extended Character Set)이라고 불리는 일종의 확장 아스키 부호 집합을 자사의 PC용으로 사용하고, 애플 컴퓨터 회사는 이와 비슷한 다른 확장 아스키 문자 집합을 매킨토시용으로 사용하고 있다. 그러므로 표준 아스키 부호 집합은 마이크로컴퓨터 하드웨어 및 소프트웨어 사이에서 세계적으로 통용되는 데 비해, 확장 아스키 부호 집합은 프로그램이나 컴퓨터 또는 인쇄기가 그것을 해독할 수 있도록 설계되어 있어야만 올바로 해독될 수 있다.



UNICODE
국제 표준으로 제정된 2바이트계 만국 공통의 국제 문자 세트(UCS)를 줄여서 부르는 이름. 미국의 애플 컴퓨터 회사, IBM사, 마이크로소프트사 등이 제안하여, 이를 추진하기 위하여 설립된 유니코드(Unicode)사가 1990년에 발표한 유니코드 버전을 ISO/IEC JTC 1에서 1995년 9월에 국제 표준으로 제정했다. 공식 명칭은 만국 복수 옥텟 부호 문자 집합(ISO/IEC 10646-1)이다. 문자 하나하나에 부여되는 데이터값을 모두 16비트로 통일하여 문자 간 호환성을 제고함으로써 컴퓨터에 의한 데이터의 교환을 원활하게 하기 위한 것이다. 즉, 현재 각국에서 사용 중인 코드의 1문자당 값이 영어는 7비트, 비영어는 8비트, 한글이나 일본어는 16비트 등으로 각기 다른 것을 모두 16비트로 통일한 것이다. ISO/IEC 10646-1의 문자판(plane)은 전 세계에서 사용하고 있는 26개 언어의 문자와 특수 기호에 대해 일일이 코드값을 부여하고 있다(최대 수용 문자 수는 6만 5536자). 여기에 포함된 한글 코드 체계는 ㉠옛 한글의 자모를 포함한 한글 자모 240자(HANGUL JAMO, 11열), ㉡현재 한국 표준인 KSC 5601의 조합형 한글 자모 94자(HANGUL COMPATIBILITY, 31열), ㉢현재 한글에서 구현할 수 있는 최대 글자 수 1만 1172자를 가나다순으로 배열해 놓은 완성형(HANGUL, AC열∼D7열) 등 3종으로 되어 있다. 또한 각국의 문자를 2바이트로 수용하기 위해 한·중·일·대만의 한자를 통합하였다. 즉 한국, 일본, 중국, 대만에서 쓰고 있는 닮은 한자에 대해 동일한 2바이트의 코드를 할당하였다.

"입력값이 제대로 출력이 안됩니다." , "무한루프를 돌아요"

많은 C입문자가 프로그래밍을 하다 물어본다는 사실을 알게 되었다..

간단한 경우는 프로그래머의 실수로 출력부분이나, 루프 탈출조건을 잘못 설정 했을 경우지만

많은 경우가 scanf에서 일어나는 것을 인지 하지 못한다는 사실에 글을 작성해본다.

scanf는 int형 자료와 char형 자료를 모두 입력 받을 수 있다.

"가장 많이 실수하게 되는 부분이 int형 입력에 char형을 입력하는 경우이다."

char형도 내부적으로 ASCII 값으로 변환되어 처리가 되기 떄문에

"Syntax Error" 나 "Compile Error" 는 발생하지 않는 경우가 대부분이다.

그렇지만, 사용자가 요구하는 입력 데이터형이 int형일 경우

"Run Time Error"가 생기게 된다.

이를 방지하기 위해서는 Valid Input을 해주어야 하는데.

이것은 scanf라는 library함수가 어떻게 동작하는지를 살펴보면 간단히 해결할 수 있다.

scanf는 가변인자와 return value를 갖는다.

다음은 MSDN에서 발췌한 내용이다.

Return Value

Both scanf and wscanf return the number of fields successfully converted and assigned; the return value does not include fields that were read but not assigned. A return value of 0 indicates that no fields were assigned. The return value is EOF for an error or if the end-of-file character or the end-of-string character is encountered in the first attempt to read a character.

scanf는 성공적으로 입력을 받으면 field의 갯수(서식명세서의 갯수) 를 return하고 error 발생시 0을 리턴하게 되므로 정상적으로 입력이 되었는지 알아보기 위해 scanf의 return 값이 0인지 아닌지를 확인해주기만 하면 된다.

따라서, 코드 내부에 다음과 같은 valid input code를 삽입해 주면 된다.



여기서 한가지 주의할 점은, 이 프로그램은 입력 값을 1개로 받고 있다.
입력을 2 2 2와 같은 식으로 입력하여도 input에 저장되고 남은 2 2는 버려지게 된다.
즉, scanf는 1개의 필드값에 정상적으로 값을 입력했다는 표시로 필드의 갯수 1을 return 한다.

C로 배우는 알고리즘 예제. [단순 연결 리스트 응용 : 명함 관리]

1.입력  2.검색  3.삭제  4.저장  5.불러오기  6.화면출력  7.프린터 출력  8.종료



Visual C++ 6.0 에서 stdprn 에러...

x86

위키백과 ― 우리 모두의 백과사전.

x86 아키텍처
16비트
1978   IA-16
1982   실제 모드
  비실제 모드
  보호 모드
32비트
1986   IA-32
  가상 8086 모드
1989   부동 소수점 장치 내장
1995   물리 주소 확장(PAE)
1997   MMX
1997   3DNow!
1999   SSE
2000   SSE2
2001   3DNow! 프로페셔널
2004   SSE3
2006   SSE4
64비트 (x64)
2003   AMD64 (x86-64)
  롱 모드
  NX 비트
2004   EM64T (IA-32e)
범례:   아키텍처
  프로세서 모드
  명령 집합

x86 또는 80x86인텔이 개발한 마이크로프로세서 계열을 부르는 말이자, 이들과 호환되는 프로세서들에서 사용한 명령 집합 아키텍처들을 통칭하는 말이다. x86 아키텍처는 데스크톱 컴퓨터 시장에서 매우 널리 쓰이며, PowerPC 같이 좀 더 근대적인 아키텍처를 사용한 프로세서들이 x86과 경쟁했으나 그다지 많은 시장 점유율을 확보하지는 못 했다.

x86 또는 80x86이라는 이름은 여기에 속하는 초기의 프로세서들 이름이 모두 80으로 시작해서 86으로 끝났기 때문에 붙여졌다. 여기에는 8086, 80186, 80286, 386, 486이 포함되며, 숫자로 상표를 등록할 수 없었기 때문에 그 뒤로는 펜티엄과 같은 별도의 이름을 사용하게 되었다. 그러나 586, 686과 같은 이름은 아직까지도 (비공식적으로) 사용되며, 전체 아키텍처를 나타내는 말에도 그 흔적이 남아 있다.

x86 아키텍처를 사용하는 최초의 프로세서는 1978년에 발표된 인텔 8086으로, 이전 프로세서인 인텔 8080로 만든 프로그램을 기계적으로 번역하면 동작하도록 설계되었다. 인텔 8086은 3년 후에 IBM PC의 표준 프로세서로 채택되었다. IBM PC는 그 후로 계속 성장하여 개인용 컴퓨터 업계의 표준이 되었으며, 그에 따라 x86 아키텍처는 매우 성공적인 명령 집합 아키텍처가 되었다. 사이릭스, 일본 전기 주식회사(NEC), IBM, 트랜스메타 등의 회사들이 x86 아키텍처를 사용하는 프로세서를 생산했으며, 그 중 AMD애슬론 계열 프로세서들은 펜티엄에 미치지는 못 하지만 상당한 시장 점유율을 차지하고 있다.

x86 아키텍처는 가변 길이 명령을 쓰는 CISC 설계를 채용했으며, 하위 호환성에 중점을 두고 있다. x86 아키텍처는 다른 아키텍처와 같이 워드 경계에 맞춰서 메모리를 읽는 것이 효율적이긴 하지만, 워드 경계에 걸치는 메모리도 한 번에 접근할 수 있다. 워드들은 최하위 바이트부터 최상위 바이트까지 순서대로 (리틀 엔디안) 저장된다. 현재의 x86 프로세서들은 명령들을 내부적으로 더 작은 단위로 쪼개서 RISC와 비슷한 내부 아키텍처에서 수행한다.

목차

[숨기기]

[편집] IA-16

인텔 8086과 8088은 14개의 16비트 레지스터를 지원했다. 네 개(AX, BX, CX, DX)는 일반 레지스터로, 실제로는 명령에 따라서 특수하게 쓰이기도 한다. (예를 들어 LOOP 명령은 항상 CX 레지스터를 사용한다.) 일반 레지스터는 상위 8비트(AH, BH, CH, DH)와 하위 8비트(AL, BL, CL, DL)를 따로 접근할 수도 있다. 네 개의 세그먼트 레지스터(CS, DS, SS, ES)는 메모리 주소의 기준을 정하는 데 사용하며, 두 개의 포인터 레지스터(SP, BP)는 메모리 주소를 담는 데 쓰인다. 두 개의 레지스터(SI, DI)는 배열을 참조하는 데 쓰이고, FLAGS 레지스터자리올림, 오버플로우 등의 상태 비트를 담고 있으며, 마지막으로 IP 레지스터는 현재 명령의 주소를 가리킨다.

당시 8086은 64 KiB(65,536 바이트)의 메모리 공간과 64 KiB의 스택 공간을 지원했다. 스택에는 한 워드 단위로 값을 넣거나 뺄 수 있으며, 스택의 꼭대기는 SS:SP에 저장된다. 스택에 값을 넣으면 SS:SP는 줄어들며, 따라서 스택의 꼭대기는 실제로는 전체 스택에서 가장 주소가 작은 곳에 있다. 256개의 하드웨어 및 소프트웨어 인터럽트가 지원되며, 반환 주소를 스택에 저장하기 때문에 한 인터럽트가 처리되는 도중에 다른 인터럽트가 걸릴 수도 있었다.

[편집] 실제 모드

인텔 80286부터는 실제 모드(real mode)라는 프로세서 모드가 부팅시 기본으로 활성화되었다. 실제 모드는 20비트의 주소를 사용할 수 있으며, 따라서 1MiB의 메모리를 접근할 수 있다. 또한 바이오스 루틴과 주변 장치를 소프트웨어적으로 직접 접근할 수 있으나, 멀티태스킹과 같은 개념을 하드웨어적으로 지원하지는 않는다.

실제 모드에서는 메모리를 접근할 때 해당하는 16비트 세그먼트와 실제로 사용되는 16비트 주소를 합쳐서 20비트 주소를 만드는데, 예를 들어 DS가 A000h이고 SI가 5677h이면 DS:SI는 DS × 16 + SI = A5677h라는 주소를 가리키게 된다. 메모리 접근에 사용할 수 있는 세그먼트는 네 개(CS, DS, ES, SS) 있으며, 그 중 CS와 SS는 프로그램 실행에 밀접한 연관이 있기 때문에 실제로는 DS와 ES만을 사용했다.

실제 모드는 프로그래밍과 컴파일러 설계를 어렵게 했는데, 현재 세그먼트 안에서 접근할 수 있는가 없는가를 검사해서 다른 명령을 만들어야 했기 때문이다. 이 때 세그먼트 안에서 접근할 수 있는 포인터를 가까운 포인터(near pointer)라 하고, 그렇지 않으면 먼 포인터(far pointer)라 한다. 이런 종류의 전환 과정은 그 뒤의 아키텍처에서도 종종 나타나는데 (예를 들어 MMX의 EEMS 명령) 결과적으로 프로그래밍을 더 어렵게 했다.

[편집] 보호 모드

인텔 80286은 실제 모드 외에도 보호 모드(protected mode)라는 별도의 모드를 하나 더 제공했다. 이 모드에서는 16 MiB물리 메모리와 1 GiB가상 메모리를 접근할 수 있으며, 세그먼트 레지스터를 별도의 세그먼트 테이블에 있는 기준 주소를 가리키도록 하였다. 이러한 테이블은 전역 기술 테이블(GDT)과 지역 기술 테이블(LDT)의 두 개로 나뉘는데, 각각 8192개의 24비트짜리 세그먼트 기술자를 담고 있으며 한 세그먼트는 64 KiB의 메모리를 담고 있다. 각 세그먼트 별로 네 개의 보호 단계(ring) 중 하나가 부여되며, 이를 통해 해당 세그먼트에서 실행되는 코드의 권한을 설정할 수 있다.

OS/2와 같은 운영 체제는 실제 모드와 보호 모드를 전환하면서 함께 쓰려는 시도를 했으나, 느릴 뿐만 아니라 실제 모드의 프로그램이 자주 컴퓨터를 중단시켰기 때문에 안전하지도 않았다. 윈도 3.0에서는 실제 모드에서 만들어진 프로그램을 보호 모드에서 돌려야 했는데, 이는 윈도 3.0이 소프트웨어적으로 가상 메모리를 구현했으며, 따라서 실제 모드에서 절대 주소를 접근하면 위험했기 때문이다. 윈도 3.1에서는 실제 모드를 사용하지 않는다.

[편집] IA-32

이 부분의 본문은 IA-32입니다.

인텔 80386은 이전 아키텍처를 32비트 환경으로 확장했다. 모든 레지스터와 명령, I/O 공간 및 메모리는 32비트로 확장되었으며, 보호 모드 역시 32비트 보호 모드로 확장되었다. 보호 모드에서 각각의 프로그램은 4 GiB의 메모리를 접근할 수 있으며, 페이징을 지원하기 때문에 가상 메모리를 하드웨어적으로 구현할 수 있다. 예외적으로 인텔 80386SX는 비용 절감을 위해 24비트 주소와 16비트 데이터 버스를 사용한다.

새로운 일반 레지스터가 추가되지는 않았으나, 모든 16비트 레지스터는 32비트로 확장되었다. 확장된 32비트 레지스터는 원래 16비트 레지스터 이름 앞에 ‘E’를 붙여서 EAX, ESI 등의 이름을 사용한다. 명령 집합은 하위 호환성을 위해 16비트와 32비트 명령을 서로 전환할 수 있도록 고쳐졌고, 명령 앞에 특수한 접두어를 붙여서 16비트 상태에서 32비트 명령을 쓰거나 32비트 상태에서 16비트 명령을 사용할 수도 있다.

인텔 80386은 세그먼트 메모리 모델과 페이징을 지원한 첫 프로세서였으며, 이 기능들은 멀티태스킹 운영 체제에 필수적이었다. 리눅스, 386BSD, 윈도 NT 등이 386 이상의 프로세서를 대상으로 개발되었고, 그 이후로 386 아키텍처는 x86 아키텍처의 기반이 되었다. 인텔 80486에서는 기존의 인텔 80387 같은 코프로세서부동 소수점 장치(FPU)로 통합되었다. 예외적으로 인텔 80486SX는 비용 절감을 위해 FPU를 지원하지 않았다.

[편집] SIMD 명령 집합

80486 이후 x86 아키텍처에는 멀티미디어 처리를 위한 SIMD 명령 집합과 이를 위한 레지스터들이 추가되었다. 이 중 처음으로 도입된 것은 펜티엄 MMX에서 소개된 MMX로, 부동 소수점 레지스터와 공유되는 64비트 정수 레지스터 8개(MM0부터 MM7까지)와 이들을 다루는 명령들이 추가되었다. 또한 중앙처리장치의 벡터 처리가 중요해지면서 AMD에서는 부동 소수점 실수를 처리하기 위한 3DNow! 확장을 만들었으며, 새로운 128비트 정수 및 실수 레지스터를 사용하는 SSE, SSE2, SSE3 등의 확장도 등장했다.

[편집] 64비트 확장

IA-32는 4 GiB의 메모리만 접근할 수 있으며, 따라서 대규모의 처리가 필요한 멀티미디어 작업이나 데이터베이스 등에 한계를 드러냈다. IA-32에서 최대 64 GiB의 메모리를 접근할 수 있게 한 물리 주소 확장(PAE) 등도 등장했지만 근본적인 해결책은 되지 못 했다.

이에 따라 IA-32의 64비트 확장이 여럿 제안되었는데, 보통 이들을 통칭하여 x64라 부른다. 대표적으로 완전히 새로운 설계를 사용하며 하위 호환성이 보장되지 않는 인텔IA-64와, IA-32와 호환되면서 64비트 명령을 추가한 AMDAMD64가 있다. IA-64를 사용하는 아이테니엄 계열 프로세서들은 IA-32를 하드웨어적으로 지원하기는 하지만 상당히 느리다. 2004년에 인텔은 AMD64의 구현인 EM64T를 발표했으며, 데스크톱 환경에서는 펜티엄 4부터 지원하고 있다.

[편집] 같이 보기

[편집] 바깥 고리

+ Recent posts