IT

C 및 C ++ 컴파일러가 강제로 적용되지 않을 때 함수 시그니처에서 배열 길이를 허용하는 이유는 무엇입니까?

lottoking 2020. 7. 2. 07:26
반응형

C 및 C ++ 컴파일러가 강제로 적용되지 않을 때 함수 시그니처에서 배열 길이를 허용하는 이유는 무엇입니까?


이것이 제가 학습 기간 동안 찾은 것입니다 :

#include<iostream>
using namespace std;
int dis(char a[1])
{
    int length = strlen(a);
    char c = a[2];
    return length;
}
int main()
{
    char b[4] = "abc";
    int c = dis(b);
    cout << c;
    return 0;
}  

따라서 변수 int dis(char a[1])에서는 사용할 수 있기 때문에 [1]아무것도하지 않는 것 같고 전혀 작동하지 않습니다 . 또는 처럼 . 배열 이름이 포인터이며 배열을 전달하는 방법을 알고 있으므로 내 퍼즐은이 부분에 관한 것이 아닙니다.
a[2]int a[]char *a

내가 알고 싶은 것은 컴파일러 가이 동작 ( int a[1])을 허용하는 이유 입니다. 아니면 내가 모르는 다른 의미가 있습니까?


배열을 함수에 전달하기위한 구문입니다.

실제로 C에서는 배열을 전달할 수 없습니다. 배열을 전달해야하는 것처럼 보이는 구문을 작성하면 실제로 배열의 첫 번째 요소에 대한 포인터가 대신 전달됩니다.

포인터는 길이 정보를 포함하지 않기 때문에 []함수 형식 매개 변수 목록에있는 내용 은 실제로 무시됩니다.

이 구문을 허용하기로 한 결정은 1970 년대에 이루어졌으며 그 이후로 많은 혼란을 야기했습니다.


첫 번째 차원의 길이는 무시되지만 컴파일러가 오프셋을 올바르게 계산하려면 추가 차원의 길이가 필요합니다. 다음 예제에서 foo함수에는 2 차원 배열에 대한 포인터가 전달됩니다.

#include <stdio.h>

void foo(int args[10][20])
{
    printf("%zd\n", sizeof(args[0]));
}

int main(int argc, char **argv)
{
    int a[2][20];
    foo(a);
    return 0;
}

첫 번째 차원의 크기 [10]는 무시됩니다. 컴파일러는 끝에서 색인을 생성하지 못하게하지 않습니다 (공식은 10 요소를 원하지만 실제는 2 개만 제공한다는 점에 유의하십시오). 그러나 두 번째 차원의 크기는 [20]각 행의 보폭을 결정하는 데 사용되며 여기서 공식은 실제와 일치해야합니다. 다시 말하지만 컴파일러는 두 번째 차원의 끝에서 색인을 생성하지 못하게하지 않습니다.

배열의베이스에서 요소 args[row][col]의 바이트 오프셋 은 다음에 의해 결정됩니다.

sizeof(int)*(col + 20*row)

인 경우 col >= 20실제로 다음 행으로 색인을 생성합니다 (또는 전체 배열의 끝에서 벗어남).

sizeof(args[0]), 80내 컴퓨터 에서을 ( ) 반환합니다 sizeof(int) == 4. 그러나을 시도 sizeof(args)하면 다음과 같은 컴파일러 경고가 나타납니다.

foo.c:5:27: warning: sizeof on array function parameter will return size of 'int (*)[20]' instead of 'int [10][20]' [-Wsizeof-array-argument]
    printf("%zd\n", sizeof(args));
                          ^
foo.c:3:14: note: declared here
void foo(int args[10][20])
             ^
1 warning generated.

Here, the compiler is warning that it is only going to give the size of the pointer into which the array has decayed instead of the size of the array itself.


The problem and how to overcome it in C++

The problem has been explained extensively by pat and Matt. The compiler is basically ignoring the first dimension of the array's size effectively ignoring the size of the passed argument.

In C++, on the other hand, you can easily overcome this limitation in two ways:

  • using references
  • using std::array (since C++11)

References

If your function is only trying to read or modify an existing array (not copying it) you can easily use references.

For example, let's assume you want to have a function that resets an array of ten ints setting every element to 0. You can easily do that by using the following function signature:

void reset(int (&array)[10]) { ... }

Not only this will work just fine, but it will also enforce the dimension of the array.

You can also make use of templates to make the above code generic:

template<class Type, std::size_t N>
void reset(Type (&array)[N]) { ... }

And finally you can take advantage of const correctness. Let's consider a function that prints an array of 10 elements:

void show(const int (&array)[10]) { ... }

By applying the const qualifier we are preventing possible modifications.


The standard library class for arrays

If you consider the above syntax both ugly and unnecessary, as I do, we can throw it in the can and use std::array instead (since C++11).

Here's the refactored code:

void reset(std::array<int, 10>& array) { ... }
void show(std::array<int, 10> const& array) { ... }

Isn't it wonderful? Not to mention that the generic code trick I've taught you earlier, still works:

template<class Type, std::size_t N>
void reset(std::array<Type, N>& array) { ... }

template<class Type, std::size_t N>
void show(const std::array<Type, N>& array) { ... }

Not only that, but you get copy and move semantic for free. :)

void copy(std::array<Type, N> array) {
    // a copy of the original passed array 
    // is made and can be dealt with indipendently
    // from the original
}

So, what are you waiting for? Go use std::array.


It's a fun feature of C that allows you to effectively shoot yourself in the foot if you're so inclined.

I think the reason is that C is just a step above assembly language. Size checking and similar safety features have been removed to allow for peak performance, which isn't a bad thing if the programmer is being very diligent.

Also, assigning a size to the function argument has the advantage that when the function is used by another programmer, there's a chance they'll notice a size restriction. Just using a pointer doesn't convey that information to the next programmer.


First, C never checks array bounds. Doesn't matter if they are local, global, static, parameters, whatever. Checking array bounds means more processing, and C is supposed to be very efficient, so array bounds checking is done by the programmer when needed.

Second, there is a trick that makes it possible to pass-by-value an array to a function. It is also possible to return-by-value an array from a function. You just need to create a new data type using struct. For example:

typedef struct {
  int a[10];
} myarray_t;

myarray_t my_function(myarray_t foo) {

  myarray_t bar;

  ...

  return bar;

}

You have to access the elements like this: foo.a[1]. The extra ".a" might look weird, but this trick adds great functionality to the C language.


To tell the compiler that myArray points to an array of at least 10 ints:

void bar(int myArray[static 10])

A good compiler should give you a warning if you access myArray [10]. Without the "static" keyword, the 10 would mean nothing at all.


This is a well-known "feature" of C, passed over to C++ because C++ is supposed to correctly compile C code.

Problem arises from several aspects:

  1. An array name is supposed to be completely equivalent to a pointer.
  2. C is supposed to be fast, originally developerd to be a kind of "high-level Assembler" (especially designed to write the first "portable Operating System": Unix), so it is not supposed to insert "hidden" code; runtime range checking is thus "forbidden".
  3. Machine code generrated to access a static array or a dynamic one (either in the stack or allocated) is actually different.
  4. Since the called function cannot know the "kind" of array passed as argument everything is supposed to be a pointer and treated as such.

You could say arrays are not really supported in C (this is not really true, as I was saying before, but it is a good approximation); an array is really treated as a pointer to a block of data and accessed using pointer arithmetic. Since C does NOT have any form of RTTI You have to declare the size of the array element in the function prototype (to support pointer arithmetic). This is even "more true" for multidimensional arrays.

Anyway all above is not really true anymore :p

Most modern C/C++ compilers do support bounds checking, but standards require it to be off by default (for backward compatibility). Reasonably recent versions of gcc, for example, do compile-time range checking with "-O3 -Wall -Wextra" and full run-time bounds checking with "-fbounds-checking".


C will not only transform a parameter of type int[5] into *int; given the declaration typedef int intArray5[5];, it will transform a parameter of type intArray5 to *int as well. There are some situations where this behavior, although odd, is useful (especially with things like the va_list defined in stdargs.h, which some implementations define as an array). It would be illogical to allow as a parameter a type defined as int[5] (ignoring the dimension) but not allow int[5] to be specified directly.

I find C's handling of parameters of array type to be absurd, but it's a consequence of efforts to take an ad-hoc language, large parts of which weren't particularly well-defined or thought-out, and try to come up with behavioral specifications that are consistent with what existing implementations did for existing programs. Many of the quirks of C make sense when viewed in that light, particularly if one considers that when many of them were invented, large parts of the language we know today didn't exist yet. From what I understand, in the predecessor to C, called BCPL, compilers didn't really keep track of variable types very well. A declaration int arr[5]; was equivalent to int anonymousAllocation[5],*arr = anonymousAllocation;; once the allocation was set aside. the compiler neither knew nor cared whether arr was a pointer or an array. When accessed as either arr[x] or *arr, it would be regarded as a pointer regardless of how it was declared.


One thing that hasn't been answered yet is the actual question.

The answers already given explain that arrays cannot be passed by value to a function in either C or C++. They also explain that a parameter declared as int[] is treated as if it had type int *, and that a variable of type int[] can be passed to such a function.

But they don't explain why it has never been made an error to explicitly provide an array length.

void f(int *); // makes perfect sense
void f(int []); // sort of makes sense
void f(int [10]); // makes no sense

Why isn't the last of these an error?

A reason for that is that it causes problems with typedefs.

typedef int myarray[10];
void f(myarray array);

If it were an error to specify the array length in function parameters, you would not be able to use the myarray name in the function parameter. And since some implementations use array types for standard library types such as va_list, and all implementations are required to make jmp_buf an array type, it would be very problematic if there were no standard way of declaring function parameters using those names: without that ability, there could not be a portable implementation of functions such as vprintf.

참고URL : https://stackoverflow.com/questions/22677415/why-do-c-and-c-compilers-allow-array-lengths-in-function-signatures-when-they

반응형