LINUX.ORG.RU

После многих лет проганья на C++ я узнал, что C массивы работают не совсем так, как я думал

 


7

4

Я всегда думал, что в C массивы, определенные как-то так int array[13] = {0}; это на самом деле то же самое, что int*, то есть память выделяется на стеке, и где-то там есть локальная переменная array, указывающая на кусок памяти под 13 интов. И я думал, что единственная разница между массивом и указателем это то, что для массива переопределен оператор sizeof, и соответственно компилятор статически знает его размер.

Сегодня я учился работать в gdb, рассматривал там всякие значения и адреса переменных, и тут конфуз у меня случился. Есть переменная fptr ptrs[3] = { NULL, get_wisdom, put_wisdom };, остановился я в gdb на брейкпоинте, и пишу

(gdb) print ptrs
$12 = {0, 0x804857e <get_wisdom>, 0x8048627 <put_wisdom>}

И думаю: «хмм, странно, почему этот gdb по умолчанию печатает массив/указатель как массив? Пробую print /a ptrs - не получается. Ну ладно, думаю я, видимо в gdb так переопределен принт для массивов, определенных как массивы, и /a почему-то не работает, ну да и хрен с ним.

Пробую затем

(gdb) print &ptrs
$13 = (fptr (*)[3]) 0x804a0d4

и думаю: „ага, почему-то чтобы мне получить значение указателя ptrs, надо написать & - странно плохо сделали тупо, а теперь я хочу получить адрес собственно переменной ptrs, а не ее значение:

(gdb) print &&ptrs
A syntax error in expression, near `&&ptrs'.

А оно не работает. Ну тут я начинаю понимать, что что-то здесь нечисто, и возможно я неправильно понимаю C-шные массивы. Иду и создаю на тест программу:

#include <stdio.h>

int main() {
    int array[5] = {0};
    printf("array = %p\n", array);
    printf("&array = %p\n", &array);
};

запускаю ее, а оно выдает

array = 0xbffff530
&array = 0xbffff530

Мамочки, думаю я, так что же, когда в Си объявляешь массив как массив, то он на самом деле не указатель, там нет никакой переменной в памяти, хранящей этот адрес, а есть лишь кусок памяти под 13 или сколько там элементов на стеке, и компилятор статически знает его адрес и при индексации прибавляет к нему сдвиг? И соответственно можно получить лишь адрес этого куска памяти, и этот адрес нигде как переменная не хранится. И соответственно переприсвоить массиву указатель на что-то другое нельзя.

array = calloc(6, sizeof(int));
//a.c: In function ‘main’:
//a.c:7: warning: incompatible implicit declaration of built-in function ‘calloc’
//a.c:7: error: incompatible types in assignment

или

printf("&&array = %p\n", &&array);
// какая-то там ошибка про label'ы, видимо && в C для гоуту используется? не знаю

Вот так я узнал сильно новую для себя вещь про C. Надеюсь, еще через пару лет не найду какой-нибудь нежданчик например в аргументах функций как массивах (вместо указателей) или в чем-нибудь еще.

А вообще мне непонятно, нафиг они так сделали? По-моему если бы были только указатели, без массивов, то было бы проще и удобнее. Ну конечно какая-нибудь там конструкция типа sizeof пускай работает, чтобы можно было статически размер их получать.

Я всегда думал, что в C массивы, определенные как-то так int

array[13] = {0}; это на самом деле то же самое, что int*

что заставило тебя так думать?

conalex ★★★
()
Ответ на: комментарий от conalex

Не уверен. Учил C довольно давно, и вроде когда нам пытались объяснить массивы и указатели, то говорили, что массивы это на самом деле и есть указатели, просто с сахарком. И вроде в интернете я что-то такое читал, и за все эти годы (тащемта с C и с C-массивами я не так часто работал) я не встретил ситуации, которая опровергла бы мои мысли об этом, ну и я был довольно твердо уверен, что прилично знаю C.

hlebushek ★★
() автор топика

А ещё function и &function, как страшно жить.

anonymous
()

Это про мифическую близость к железу, которой нет.

Joe_Bishop
()

А вообще мне непонятно, нафиг они так сделали?

Как это нафиг? А как тогда сделать так, чтобы «там нет никакой переменной в памяти, хранящей этот адрес, а есть лишь кусок памяти под 13 или сколько там элементов на стеке, и компилятор статически знает его адрес и при индексации прибавляет к нему сдвиг»?

Sorcerer ★★★★★
()
Ответ на: комментарий от Sorcerer

А зачем? 4/8 байтиков под указатель экономим?

С хоть немного оптимизирующим компилятором не нужно. Если я где-нибудь в коде делаю &array, то пускай выделяет 4/8 байтов под хранение адреса, а если не делаю, то пусть инлайнит адрес во все места, где происходит обращение по индексу.

hlebushek ★★
() автор топика
Последнее исправление: hlebushek (всего исправлений: 1)
Ответ на: комментарий от Radjah

Указатель на пачку указателей на данные это char** argv aka char* argv[], и я до сих пор не знаю, отличаются ли эти варианты в качестве параметра функции чем-нибудь кроме того, что я все время забываю, там массив указателей или указатель на массивы (хотя на деле очевидно, что массив указателей, потому что если бы другой вариант был легален, то можно было бы делать и char argv[][] или char two_dimension_array[3][5], но этого нельзя).

hlebushek ★★
() автор топика
Ответ на: комментарий от hlebushek

А зачем?

А почему нет? Си должен это позволять, иначе он не был бы Си. А если очень хочется объявить указатель на массив в стеке, то никто не мешает:

int *array = (int [5]){0};

Sorcerer ★★★★★
()
Ответ на: комментарий от Sorcerer

А почему нет? Си должен это позволять, иначе он не был бы Си.

Проверять флаги переполнения он почему-то не позволяет.

hlebushek ★★
() автор топика
Ответ на: комментарий от prischeyadro

Не читал. Что это вообще за сайт, и зачем его читать?

А вообще и правда неплохо написано, вот этот сниппет с картинкой все поясняют:

char a[] = "hello";
char *p = "world";

http://c-faq.com/aryptr/aryptrstring.gif

hlebushek ★★
() автор топика
Ответ на: комментарий от hlebushek

Проверять флаги переполнения он почему-то не позволяет.

Эта фича есть не на всех архитектурах.

Sorcerer ★★★★★
()

это на самом деле то же самое, что int*, то есть память выделяется на стеке

есть лишь кусок памяти под 13 или сколько там элементов на стеке

Логика где?

Leron ★★
()

k&r(1st ed)|file2txt|grep -c 3 "pointer"

из introduction chapter 0:

C provides pointers and the ability to do addres arithmetic. The arguments to function are passed by copying the value of the argument in the caller. When it is disired to achieve «call by reference,» a pointer may be passed explicitly, and the function may change the object to which the pointer points. Array names are passed as the location of the array origin, so array arguments are effectively call by reference.

из chapter 5 pointers and arrays

прям 1st предложение

A pointer is a VARIABLE that contains the address of another variable/

из 5.3 Pointers and Arrays

In C, there is a strong relationship between pointers and arrays, strong enough that pointers and arrays really should be treated simultaneously. Any operation which can be achievid by array subscripting can also be done with pointers.

...

In fact, a reference to an array is converted by the compiler to a pointer to the beginning of the array. The effect is that and array name is a pointer expression.

...

      int a[10];
      int *pa;

Since the name of an array is a synonym for the location of the zeroth element, the assigment

       pa = &a[0];

can also be written as

       pa = a;

Следующий абзац(в k&r) не С++14 дружный.

....

There is one difference between an array name and a pointer that must be kept in mind. A pointer is a variable, so pa=a and pa++ are sensible operations. But an array name is constant, not a variable: consturctions like a=pa or a++ or p=&a are illegal.

When an array name is passed to function, what is passed is the location of the beginning of the array. Within the called function, this argument is a variable, just like any other variable, and so an array name argument is truly a pointer, that is, a variable containing an address.

----------------------------------------------- как автоматизировать в сырец определение #define UNDEGRAUNDBYTES тут какой коркодил.

/*дублирует аргументы DynArr c положеными падингами промеж */
struct args{
.
.
.
}

struct Tel{
   struct args{...} args;
   char[UNDEGRAUNDBYTES] padding;
}


int DynArr(int a, int b, int c){
        if some(){
                return DynArr(. . .)
        }
        Tel *pa=(Tel*)&a;
        /* теперь pa[0] .. pa[Глубена рекусрсии-1] массив записей активации функции DynArr - можно и аргументы через голову по возвращать - ну а уж если разить в адресса возврата то и аля "break n"



}
qulinxao ★★☆
()
Ответ на: комментарий от hlebushek

Допустим, есть микроконтроллер, адрес «весит» 2 байта, а памяти ВСЕГО доступно 4 кб. Лишних 2 байта - это лишних 0,05% общей доступной памяти, расходуемой, в данном случае, вообще ни на что, т.к. не придаёт ни удобства программисту, ни компилятору. И так на каждый массив. Весьма накладно, не правда ли?

А теперь смотрим, какие компьютеры были доступны в 72-83 году во времена актуальности обсуждаемого языка программирования.

anonymous
()

(gdb) print &&ptrs

А оно не работает.

Дальше не читал. ТС, похапе твое всё.

tailgunner ★★★★★
()

Зачем к массиву прибивать гвоздями указатель если он там не нужен, а так есть массивы есть указатели - структуру разные и прекрасно работают. А вот если к массиву прибить указатель гвоздями то не получится тогда писать чудесные структуры для данных переменной длинны типа:

struct MyDataPacket
{
  int packetSize;
  int packetCode;
  char packetData[0];
}

zaz ★★★★
()

Давай я тебе ещё раз крышу снесу, а? ;)

Структуры можно передавать в функцию не только по указателю (как это обычно делается), но и по значению:

#include <stdio.h>

struct zzz { int a, b; };

int
fun(struct zzz z)
{
        printf("fun: %d %d\n", z.a, z.b);
        z.a += z.b;
        printf("fun: %d %d\n", z.a, z.b);
        return 0;
}

int
main()
{
        struct zzz z = {2, 3};
        printf("main: %d %d\n", z.a, z.b);
        fun(z);
        printf("main: %d %d\n", z.a, z.b);
        return 0;
}

beastie ★★★★★
()

Ты когда себе в ногу стрелял тебе по указателю мозг не разнесло?

rezedent12 ☆☆☆
()
Ответ на: комментарий от hlebushek

char** argv aka char* argv[]

Простое правило есть для чтения таких и более сложных вещей: сначала читаете направо от имени переменной до закрывающей скобки, потом - налево до открывающей, затем опять направо и т.д. Любые сложные объявления после этого читаются легко и, надо понимать, синтаксический анализатор сканирует по тому же принципу.

backbone ★★★★★
()
Последнее исправление: backbone (всего исправлений: 1)
Ответ на: комментарий от beastie

Но ведь для этого есть специальные костыльные инструкции-аттрибуты компилятору - что-то там c aligned.

hlebushek ★★
() автор топика
Ответ на: комментарий от beastie

Ну это я и так знаю, я же в C++ в основном работаю.

hlebushek ★★
() автор топика

Семантика разная же

#include <stdio.h>

int main(){
	int a[5];
	printf("%p %p\n", a, &a);
	printf("%p %p", a + 1, &a + 1);
return 0;
};

Deleted
()
Ответ на: комментарий от zaz

Указатель на конец структуры, сдесь какраз начинаются данные пакета
zaz

Вот за это мы Си любим и ненавидим :)

anonymous
()

судя по: http://minnie.tuhs.org/cgi-bin/utree.pl?file=V2/c/ncc.c

поначалу не было T *pt синтаксиса, а было T p[] если нет больше нуля элементов значит не константа а слот для указателя - что выше как рудимент осталось при обьявлении в структурах смещений после которых идут row-данные.

qulinxao ★★☆
()

эталонный малок!: http://minnie.tuhs.org/cgi-bin/utree.pl?file=V4/nsys/dmr/malloc.c


struct map {
	char *m_size;
	char *m_addr;
};

malloc(mp, size)
struct map *mp;
{
	register int a;
	register struct map *bp;

	for (bp = mp; bp->m_size; bp++) {
		if (bp->m_size >= size) {
			a = bp->m_addr;
			bp->m_addr =+ size;
			if ((bp->m_size =- size) == 0)
				do {
					bp++;
					(bp-1)->m_addr = bp->m_addr;
				} while ((bp-1)->m_size = bp->m_size);
			return(a);
		}
	}
	return(0);
}

mfree(mp, size, aa)
struct map *mp;
{
	register struct map *bp;
	register int t;
	register int a;

	a = aa;
	for (bp = mp; bp->m_addr<=a && bp->m_size!=0; bp++);
	if (bp>mp && (bp-1)->m_addr+(bp-1)->m_size == a) {
		(bp-1)->m_size =+ size;
		if (a+size == bp->m_addr) {
			(bp-1)->m_size =+ bp->m_size;
			while (bp->m_size) {
				bp++;
				(bp-1)->m_addr = bp->m_addr;
				(bp-1)->m_size = bp->m_size;
			}
		}
	} else {
		if (a+size == bp->m_addr && bp->m_size) {
			bp->m_addr =- size;
			bp->m_size =+ size;
		} else if (size) do {
			t = bp->m_addr;
			bp->m_addr = a;
			a = t;
			t = bp->m_size;
			bp->m_size = size;
			bp++;
		} while (size = t);
	}
}

qulinxao ★★☆
()
Ответ на: комментарий от hlebushek

А зачем?

Исторически так сделали. Потому что в C это ещё с BCPL тянется — там был Ocode (откуда *.o) и указатели нетипизированные, вот и в Си все указатели стали совместимы друг с другом по присваиванию (у всех одинаковый размер). В общем, указатели это типонебезопасная дыра. Денис Ричи и сам признавал, «что-то мы там со ссылками недодумали, а пускай, и так сойдёт».

Ну а дальше см. k&r, да. И несовместимости С++ и С.

anonymous
()
Ответ на: комментарий от hlebushek

дело в совместимости «массива длины 0» с указателем на что угодно, как раз так. вот в Win32 API например такого добра навалом: структура с полем-размером в первых байтах, затем «массив длины 0» который по факту в рантайме интерпретируется как структура этого типа, указанной длины. в итое можно передавать например ООП классы: структуры предка и потомка с одинаковыми первымия байтами и разными длинами.

anonymous
()
Ответ на: комментарий от hlebushek

если про пакеты из примера — то UDP и TCP одинаковой, полиморфной структурой представлять.

anonymous
()

Потому что говно твой С, а массивы в нем — сливки с говна.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.