포인터를 직접 사용해서 루프를 돌려도 될까요?

mydream의 이미지

size_t insert(size_t position, T* firt, T* last)

로 해서 vector의 insert와 같은 기능을 만들려고 합니다. 포인터 first와 last로 범위를 점검해서 루프를 돌릴 수 있을까요? 물론 내부에서 first와 last의 값을 수정하는 일을 없습니다. 그냥 읽기만 하죠. 그리고 last는 읽어올 마지막 요소 다음의 주소를 의미하는데, 그렇게 하면 오버플로우가 발생할까요? 그냥 읽기만 해도요. 가령

int arrint[10];

이라는 배열이 있습니다. 그리고

vector<int> srcint(10);

으로 선언된 벡터가 있고요.

다음과 같이 하면

srcint.insert(3, &arr[5], &arr[10]);

arr[10]이 범위를 초과해서 컴파일이 안 되거나 실행시 오류가 발생하는 일은 없어야 하는데요. iterator는 사용하지 않으려 합니다.

Necromancer의 이미지

가능은 합니다만, 컴파일러나 아키텍쳐가 바뀐다거나 하는경우 머리아픈 문제 터질수 있습니다.
C에서 포인터값을 비교하는 동작은 정의가 안되어 있습니다. 이건 구현물(컴파일러) 맘이라는 뜻입니다 .

제 예전 경험으로는 gcc라면 void 포인터로 형변환하면 값 비교가 가능했습니다.

Written By the Black Knight of Destruction

 의 이미지

1. 명백히 질문자님의 코드는 C가 아니라 C++입니다.
C에는 없고 C++에만 있는 템플릿 매개변수를 강하게 암시하는 타입이름 T야 뭐 흉내낼 수 있는 거지만,
srcint.insert와 같은 멤버 함수 호출은 C++ 코드임을 보여주는 확실한 증거죠.

2. C++에서 포인터는 비교 가능합니다.
C++11 표준 기준,
> 5.9 Relational operators (<, >, <=, >=)
2 Pointers to objects or functions of the same type (after pointer conversions) can be compared, with a result defined as follows:

— If two pointers p and q of the same type point to the same object or function, or both point one past the end of the same array, or are both null, then p<=q and p>=q both yield true and p<q and p>q both yield false.

— If two pointers p and q of the same type point to different objects that are not members of the same object or elements of the same array or to different functions, or if only one of them is null, the results of p<q, p>q, p<=q, and p>=q are unspecified.

— If two pointers point to non-static data members of the same object, or to subobjects or array elements of such members, recursively, the pointer to the later declared member compares greater provided the two members have the same access control (Clause 11) and provided their class is not a union.

— If two pointers point to non-static data members of the same object with different access control (Clause 11) the result is unspecified.

— If two pointers point to non-static data members of the same union object, they compare equal (after conversion to void*, if necessary). If two pointers point to elements of the same array or one beyond the end of the array, the pointer to the object with the higher subscript compares higher.

— Other pointer comparisons are unspecified. (발췌됨)

> 5.10 Equality operators (==, !=)
1 Pointers of the same type (after pointer conversions) can be compared for equality. Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address (발췌됨)

중요한 내용 굵게 했습니다만 한국어로 요약정리:
두 포인터가 같은 타입을 가리키고 어찌됐든 간에 같은 object나 function을 가리키거나, 똑같은 배열의 one past the end 지점을 가리키고 있다면, 두 포인터를 <, >로 비교하면 false이고 <=, >=으로 비교하면 true입니다. 반면 두 포인터가 같은 배열 안의 원소 혹은 one past the end를 가리킨다면, 그 대소 관계는 배열 안에서의 인덱스 크기 대소 관계와 같습니다. ==는 더 간단해서, 같은 타입을 가리키는 두 포인터가 1) 둘 다 NULL이거나, 2) 같은 함수를 가리키거나 3) 어찌됐든 같은 주소를 가리키면 true입니다. !=는 그 반대.

여기서 주목해야 할 게, 언어 표준에 덜 익숙하다면 잘 모르실 수 있는 one past the end입니다. C/C++ 표준은 가끔 표준이 번잡해지는 걸 감수하고 프로그래머에게 편의 기능을 제공하는데, 바로 이 경우가 대표적이죠. int arrint[10];와 같이 선언&정의하였으면 arrint[0]부터 arrint[9]까지만 유효하고 사용 가능해야 할 것 같지만, 사실은 마지막 원소 arrint[9]바로 다음 주소, 그러니까 arrint+10까지도 유효하다는 거에요.

> 5.7 Additive operators
5 When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integral expression. In other words, if the expression P points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the value n) point to, respectively, the i + n-th and i − n-th elements of the array object, provided they exist. Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

바로 이게 one past the end의 개념이며, STL 컨테이너가 제공하는 end() 반복자 개념의 원형입니다. 어쨌건 이런 포인터를 만드는 건 분명히 합법적입니다.

조금 미묘한 게 하나 남긴 했어요. 일반적으로 one past the end 포인터는 역참조하면 안 됩니다. 그런데 질문자님께서 쓰신 표현 &arrint[10]는 다시 말하면 &(*(arrint+10))이거든요? 이는 내부적으로 one past the end 포인터를 일단 한 번 역참조하는 꼴이라, 솔직히 이런 표현이 표준에 의거해 안전한지 확신이 안 듭니다. 일단 g++한테 넘겨 보니까 경고를 전부 켜도 한 마디 불평도 없던데, 특정 구현환경이란 게 가끔은 표준보다 더 관대할 수도 있는 거라서 엄밀한 판단 근거라기엔 좀 부족하거든요. 그래서 지금 표준에서 관련 내용을 찾아보고 있는데 잘 모르겠어요. 제가 내공이 떨어지나 봅니다. 다른 고수님들께서 이 글을 보셨다면 알려주신다면 감사드리겠습니다. 중요하니까 강조. 대신, arrint+10는 확실히 안전합니다.

3. 요약정리
네 됩니다.

저로서는 질문자님이 왜 일부러 iterator를 배제하고 작성하시려는지는 잘 모르겠어요. iterator는 일반화된 포인터 아니던가요. 아주 약간만 신경써 주면 (포인터를 포함해) 양방향 반복자 정도까진 가볍게 지원할 수 있을 텐데요.

뭐, 짜는 사람 맘이죠. 어쨌거나 포인터만 지원할 거면 first < last 등의 조건을 무난히 사용 가능하고, 저라면 first != last를 주로 쓸 겁니다. 이 조건문은 양방향 반복자에도 적용 가능하니까요.

아, 물론, insert 함수를 호출하는 쪽에서 협조를 안 해 주면 insert 함수 안에서도 할 수 있는 게 별로 없습니다. 예컨대 first와 last를 서로 다른 배열에서 가져다 넘겨준다던가. 이런 건 insert 함수 안에서 검사할 방법조차 없기 때문에 배를 째는 수밖에 없어요. 호출자가 그런 짓을 안 하리라고 믿고 그냥 짜는거죠. STL 라이브러리 알고리즘들도 대체로 그렇게 돌아갑니다. ~_~

4. 덤
size_t insert(size_t position, const T* first, const T* last)는 어때요? const는 붙여야 제맛!

 의 이미지

질문자님이 C++ 코드로 질문하셨기 때문에 저 역시 C++ 표준 기준으로 답했습니다만
C언어에서도 포인터 비교가 가능하고, 그 상세 내용은 앞서 설명드린 C++의 그것과 거의 같습니다.

한 가지 주목할 만한 점은, C99 표준에서는 one past the end 포인터가 역참조되는 것을 금지한다고 명시적으로 서술하고 있군요.

> 6.5.6 Additive operators
8 (전략) If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

C++11 표준의 대응되는 항목에는 이 문장이 없었던 거 같은데... 대개 이런 경우 1) C++에서는 이를 허용해줘야 할 이유가 있었거나 2) C++ 표준은 그런 경우를 간접적으로 금지하는 다른 개념이 있기 때문인 경우가 많습니다. 어느 쪽인지는, 솔직히 아직 잘 모르겠네요.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.