C
by Vasil KolevТия дни ми стана интересно, дали програмистите могат да кажат каква е разликата между следните 3 фрагмента и кое в кой случай може/не може да с ползва? Най-малкото не си спомням някога да са ми го преподавали:
Фрагмент 1:
char *pesho=”PESHO”;
Фрагмент 2:
char *pesho=(char *) malloc (6);
strcpy(pesho,”PESHO”);
Фрагмент 3:
char pesho[6]=”PESHO”;
(например как в единия от случаите не трябва да се опитваме да пишем в string-а, как другия не можем да го връщаме като резултат и т.н.)
Tags: работа
January 15th, 2008 at 14:47
По твое време операционните системи със защита на паметта не бяха на почит в училищата, поради което:
char *pesho = “PESHO”;
strcat(pesho, ” is in a read-only memory page”);
обикновено сработваше без SEGV, макар и със странични ефекти, видими на много по-късен етап в работата на програмата.
January 15th, 2008 at 15:02
Обикновенно се преподава, когато се обяснява видовете памет, по-специално heap и stack. char pesho[6]=”PESHO”; и char *pesho=”PESHO”; са идентични – паметта е от стека, следователно не може да се ползват за да се върне указател (напр. return pesho), защото паметта им се освобожда точно след изилизане от функцията, която ги ползва. След това този указател може вече да не сочи към този текстов низ. В malloc случая памета е от heap-а, може да се върне указател, но пък за тази “привилегия” този който използа указателя е длъжен да каже free(pesho) за да освободи паметта когато не му е необходима. Често се забравя :(
January 15th, 2008 at 15:29
gluposti :P
char *cunt0 = “CUNT0”; slaga pointera cunt0 da sochi v RO data segmenta, kudeto se zapisva “CUNT0”, i mozhe da se vrushta bez kakvito i da bilo problemi. pisaneto e zabraneno obache i efekta shte e Bus error (SIGBUS).
v zavisimost ot tova, kude e definirano char cunt1[5] = “CUNT1”; mozhe dazhe da se vrushta pointera (globalen statichen var primerno). ako e vuv funkcija, togava return-a mu shte e… interesen :].
izobshto stack se pojavjava samo ako se slozhi tova s [] u function.
e.g. tova tuka e napulno validen kod.
#include <stdio.h>
char *a() {
char *cunt = “CUNT”;
return cunt;
}
void b() {
int i, a[1024];
for (i = 0; i < 1024; i++)
a[i] = i;
return;
}
int main() {
char *t;
t = a();
b();
printf(“%s\n”, t);
return 0;
}
sluchaja s malloc-a go ostavjam bez komentar.
кирливица :P
January 15th, 2008 at 18:03
Chervo, струва ми се, че бъркаш! Според мен има мааалка уловка тук.
Читав (модерен) компилатор, който поддържа const не би сложил:
char *rwstring = “BlahBlah”;
в РО памет, поради простата причина, че:
rwstring[0] = “5”;
е съвсем валидна операция.
Но и Свеи малко леко се е изцепил с това, че паметта била от стека. В стека е само и единствено указателя, самият стринг ще е другаде (забравих в момента как се казваше този сегмент, когато имахме 16 битови, сегментирани приложения. В 32 бит със сигурност обаче би следвало да е в RW памет).
January 15th, 2008 at 18:32
Дончо, черво според мен е прав, “BlahBlah” ще стои в RO data сегмент. А ти всъщност пробва ли въпросната валидна операция, която генерира SIGSEGV (както спомена физикът)? :-)
January 15th, 2008 at 18:40
Дончо, това с gcc не работи – винаги е в RO памет, и на 32битова система, и на 64, както и на 32битово visual studio. На някоя операционна система без защита на паметта ще работи (или ако си кажеш някоя опция да ти прави константите read/write), ама това е лоша идея :)
January 15th, 2008 at 18:43
Червото е прав и това дори до колкото ми е известно е конвенция специално за стринговете в C, че char *zaiobaio = “SOMECONSTSTRING” генерира указател към константа (т.е. към RO паметта).
Принципно с друг тип данни няма да може въобще да мине нещо от рода на type *somevar = {val1, val2}; просто това не е вярно инициализиране. Аз самия като пиша стрингове никога не ползвам char * с нещо различно от malloc / new ири директна инициализация към област в паметта или NULL. Ако ще правя указател към константа като в примера то си пиша const char* const zaiobaio – това гарантира че компилатора ще ме пази от некадърници дето искат да пишат в низа и такива които искат zaiobaio да не сочи вече към точно тоя низ.
Аз самия да се убедя в това което казвам драснах ей тая програмка :
jul@atila:~$ cat pesho.c
#include
int main()
{
char gosho[6] = “GOSHO”, *pgosho = gosho;
char *pesho=”PESHO”;
printf(“%s\n”,pgosho);
*pgosho = ‘M’;
printf(“%s\n”,pgosho);
printf(“%s\n”,pesho);
pesho++;
printf(“%s\n”,pesho);
*pesho = ‘M’;
printf(“%s\n”,pesho);
return 0;
}
и ето какво става
jul@atila:~$ ./pesho
GOSHO
MOSHO
PESHO
ESHO
Segmentation fault
Явно че самия указател може да го размъкваме напред назад, но като се опитаме да пипнем зад него и SIGSEGV-ва.
и накрая valgrind-а казва просто ей това
==4351== Process terminating with default action of signal 11 (SIGSEGV)
==4351== Bad permissions for mapped region at address 0x8048491
==4351== at 0x80483A9: main (in ./pesho)
P.S. Говоря за gcc version 4.2.3 20080102 (prerelease) (Debian 4.2.2-5)
January 15th, 2008 at 19:07
Единственото, което мога да кажа е, че явно е СИЛНО компилаторно-зависимо. Интересно какво казва стандарта по този въпрос, нямам възомжност сега да го прегледам.
Ще пробвам по-късно как ще е това на VC++…
January 15th, 2008 at 22:14
char *ptr = “PTR”;
Това записва в указателя ptr адреса на област от секцията за константи (.rodata при ELF на Solaris, FreeBSD и Linux). Секцията е маркирана като readonly, както лесно се вижда с objdump:
12 .rodata 00000010 080543b8 080543b8 000043b8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
Абсолютно същия адрес се записва на мястото на “PTR” в следващия случай, тъй като в C по подразбиране низовите константи не се дублират:
funccall(…, “PTR”, …);
char ptr[] = “PTR”;
Това е просто съкратен запис на
char ptr[] = { ‘P’, ‘T’, ‘R’, ” };
Т.е. инициализиран масив с подразбираща се дължина. Съответно ptr в случая е указател към област от паметта, разположена в секцията за данни (.data при ELF). Съответно objdump казва:
16 .data 00000178 08065250 08065250 00005250 2**3
CONTENTS, ALLOC, LOAD, DATA
Нещата стават по-ясни, когато човек погледне асемблерния код, който компилаторът произвежда (gcc -S) в трите случая. Но, хей, аз съм просто един пропаднал физик. Предполага се вие да ме учите на тези неща, а не аз вас ;)
January 15th, 2008 at 22:14
Това :
===
int main() {
char * ptr1 = “Test string”;
char * ptr2 = “Test string”;
char arr[12] = “Test string”;
//ptr1[0]=’x’;
printf(“ptr1:\t%p\n”, ptr1);
printf(“ptr2:\t%p\n”, ptr2);
printf(“arr:\t%p\n”, arr);
}
===
дава при мен следния резултат :
===
ptr1: 00401290
ptr2: 00401290
arr : 0022FF58
===
Т.е. като използваш указател в него се записва адреса на низовата константа, докато при инициализацията на масив се създава масив в стека с размер колкото да побере низа и ”, и в него се копира константата.
Все пак ми е интересно защо се допуска присвояването на адрес към константната памет в променлива, която не е const. Според Керниган и Ричи, опитът да се промени низовата константа през указател води до неопределен резултат.
January 15th, 2008 at 22:18
То и онзи физик, дето е измислил уеба, сигурно така е говорил ;)
January 15th, 2008 at 22:33
Да спестя малко труд на Дончо: C++ компилаторът на VS2008 поставя моето “PTR” в сегмент на име CONST, който в крайна сметка свършва в секция .rdata на PE файла.
January 15th, 2008 at 22:49
Напъвам се да се сетя къде бях чел защо C допуска да се създаде non-const указател към стринг които е константен, помня че съм виждал нещо по въпроса. Може би е просто за да се улесни компилатора все пак стринговите литерали са от малкото типове данни които се пазят по този начин в RO сегмента без изрично да са декларирани const, защото повечето константи се зареждат директно в регистрите при използване, а всички други композитни типове данни така или иначе изискват const експлицитно.
Апропо някой знае ли дали C (не C++) компилатора на MS се е променял в последните няколко версии от 98 насам, защото поне C++ компилатора им извървя много дълъг път за последните 10 години, което е похвално за хората на Били.
January 16th, 2008 at 05:26
maniaxe cast-a във фрагмент 2 е излишен. Не замърсявай кода malloc връща void.
January 16th, 2008 at 07:57
Днес, точно по темата, от XKCD.
January 16th, 2008 at 11:28
@gf, прав си, навик ми е от едно време, тогава ако не правех cast ми даваше warning, не помня случая какъв беше точно.
January 21st, 2008 at 22:56
майко мила !
бегом марш сичките коментатори към лудницата !
;-Р
January 22nd, 2008 at 01:22
@gf Прав си за cast-а в чистото C е излишно, но понеже тук дискусията се поомеси е добре да се каже, че в C++ не е правилно да не кастнеш void. Дори аз намирам за добра практика да се пише защото в код без ясна конвенция за именоване на промеливите, от която да си личи какъв е типа им ако срещнеш нейде из кода:
myunknownvar = malloc(100 << 2);
Е доста неясно, аз бих предпочел:
myunknownvar = (float*)malloc(100 << 2);
и накрая естесвено най доброто
myunknownvar = (float*)malloc(100 * sizeof(float));
@alex242: Ако погледнеш принципно нищо не се е променило кой знае колко, Java e скопено C++, .NET езиците стоят нейде между Basic на стероиди, C++ и Java.
Още повече се забалявам да гледам как във C# се преоткриват концепции познати ни от как има функционални езици. Например голявата оновст в C# v3 – Instance methods или еквивалента на глобални функции. А пък тоталната дегенеративност за имплицитно типизиране на локални промеливи, което за мен е пълна глупост не знам кой му е дошло на акъла, нито имаш гъвкавоста на нетипизинате промеливи като в Perl примерно, нито пък си спестяваш кои знае колко работа.