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:

18 Responses to “C”

  1. пропаднал физик Says:

    По твое време операционните системи със защита на паметта не бяха на почит в училищата, поради което:

    char *pesho = “PESHO”;
    strcat(pesho, ” is in a read-only memory page”);

    обикновено сработваше без SEGV, макар и със странични ефекти, видими на много по-късен етап в работата на програмата.

  2. Сви Says:

    Обикновенно се преподава, когато се обяснява видовете памет, по-специално heap и stack. char pesho[6]=”PESHO”; и char *pesho=”PESHO”; са идентични – паметта е от стека, следователно не може да се ползват за да се върне указател (напр. return pesho), защото паметта им се освобожда точно след изилизане от функцията, която ги ползва. След това този указател може вече да не сочи към този текстов низ. В malloc случая памета е от heap-а, може да се върне указател, но пък за тази “привилегия” този който използа указателя е длъжен да каже free(pesho) за да освободи паметта когато не му е необходима. Често се забравя :(

  3. chervo Says:

    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

  4. Дончо Says:

    Chervo, струва ми се, че бъркаш! Според мен има мааалка уловка тук.

    Читав (модерен) компилатор, който поддържа const не би сложил:
    char *rwstring = “BlahBlah”;
    в РО памет, поради простата причина, че:
    rwstring[0] = “5”;
    е съвсем валидна операция.

    Но и Свеи малко леко се е изцепил с това, че паметта била от стека. В стека е само и единствено указателя, самият стринг ще е другаде (забравих в момента как се казваше този сегмент, когато имахме 16 битови, сегментирани приложения. В 32 бит със сигурност обаче би следвало да е в RW памет).

  5. ddsad Says:

    Дончо, черво според мен е прав, “BlahBlah” ще стои в RO data сегмент. А ти всъщност пробва ли въпросната валидна операция, която генерира SIGSEGV (както спомена физикът)? :-)

  6. admin Says:

    Дончо, това с gcc не работи – винаги е в RO памет, и на 32битова система, и на 64, както и на 32битово visual studio. На някоя операционна система без защита на паметта ще работи (или ако си кажеш някоя опция да ти прави константите read/write), ама това е лоша идея :)

  7. ___Jul___ Says:

    Червото е прав и това дори до колкото ми е известно е конвенция специално за стринговете в 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)

  8. Дончо Says:

    Единственото, което мога да кажа е, че явно е СИЛНО компилаторно-зависимо. Интересно какво казва стандарта по този въпрос, нямам възомжност сега да го прегледам.

    Ще пробвам по-късно как ще е това на VC++…

  9. пропаднал физик Says:

    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) в трите случая. Но, хей, аз съм просто един пропаднал физик. Предполага се вие да ме учите на тези неща, а не аз вас ;)

  10. Ирина Марудина Says:

    Това :
    ===
    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. Според Керниган и Ричи, опитът да се промени низовата константа през указател води до неопределен резултат.

  11. Ирина Марудина Says:

    Но, хей, аз съм просто един пропаднал физик. Предполага се вие да ме учите на тези неща, а не аз вас ;)

    То и онзи физик, дето е измислил уеба, сигурно така е говорил ;)

  12. пропаднал физик Says:

    Да спестя малко труд на Дончо: C++ компилаторът на VS2008 поставя моето “PTR” в сегмент на име CONST, който в крайна сметка свършва в секция .rdata на PE файла.

  13. ___Jul___ Says:

    Напъвам се да се сетя къде бях чел защо C допуска да се създаде non-const указател към стринг които е константен, помня че съм виждал нещо по въпроса. Може би е просто за да се улесни компилатора все пак стринговите литерали са от малкото типове данни които се пазят по този начин в RO сегмента без изрично да са декларирани const, защото повечето константи се зареждат директно в регистрите при използване, а всички други композитни типове данни така или иначе изискват const експлицитно.

    Апропо някой знае ли дали C (не C++) компилатора на MS се е променял в последните няколко версии от 98 насам, защото поне C++ компилатора им извървя много дълъг път за последните 10 години, което е похвално за хората на Били.

  14. gf Says:

    maniaxe cast-a във фрагмент 2 е излишен. Не замърсявай кода malloc връща void.

  15. Дончо Says:

    Днес, точно по темата, от XKCD.

  16. admin Says:

    @gf, прав си, навик ми е от едно време, тогава ако не правех cast ми даваше warning, не помня случая какъв беше точно.

  17. alex242 Says:

    майко мила !
    бегом марш сичките коментатори към лудницата !
    ;-Р

  18. ___Jul___ Says:

    @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 примерно, нито пък си спестяваш кои знае колко работа.

Leave a Reply