Pull to refresh

Comments 26

Ошибок, которые легко допустить в C, гораздо больше четырёх :-)

Мы вполне могли хранить в качестве первых четырех символов строки байты числа, представляющего длину сообщения, которое бы следовало за ним.

То есть вы вставили в строку кусок сырых данных и удивляетесь, что строковые функции с ним некорректно работают? Вообще-то, это делается либо через структуру, либо через ручное управление памятью:
struct tx_buffer{
  uint32_t len;
  char data[DATA_SIZE];
}buf;
strcpy(buf.data, "Some string\n");
buf.len = sizeof(buf.len) + strlen(buf.data);

char buf[DATA_SIZE + 4];
strcpy(&buf[4], "Some string\n");
*((uint32_t*)buf) = strlen(&buf[4]) + 4;

char *mystr = «This is my string\n»;
mystr = mystr + 13;
*mystr = 'u';

Тут да, новички ошибаются часто. Поэтому если строка не должна быть константной, ее объявляют как массив
char mystr[] = "This is my string\n";

А если константной, то пишут const.
Чтобы вызвать ошибку, достаточно перед функции free добавить следующую строчку:
s1 = s1 + 1;
Как можно до этого додуматься мне неизвестно.
Использование локальных переменных функции за её пределами после завершения работы функции
Вот тут снова соглашусь, новички часто об этом не подозревают.
— Итого 2 ошибки, которые допускают часто (пока опыта мало, конечно) и 2, для которых надо сильно постараться.
Использование локальных переменных функции за её пределами после завершения работы функции

А как это можно сделать?
Это получается функция должна вернуть указатель на свои локальные данные! Ха, до этого надо додуматься.
Не для всех очевидно что char *var1 = var2 это копирование указателя на строку, но не самой строки. С другими локальными переменными тоже самое, хотя и реже встречается. Но в отличие примеров ходьбы по массиву или запихивания сырых данный в строку, это все же бывает.
По крайней мере у новичков, которые еще не разобрались с синтаксисом.
А реально. Вот WinAPI. Думаешь что функция сама копирует строку себе в память, а хрен там. Её надо объявить в статичной памяти или в куче, сам лажал намедни.
Я добавил пояснение по поводу ошибки с функции free. Конечно, после успешного выделения памяти, можно сохранить в другой переменной адрес начала исходного указателя. И не менять исходный указатель, а использовать новую переменную. Но, скорее всего, можно легко перепутать имена таких переменных, и вот тогда можно напороться на ошибку. Но это будет уже частным случаем неправильного именования переменных и порождения плохого кода.
char *s1 = malloc(255);
process(s1);
free(s1);

<.sarcsm>
А что если malloc вернет NULL
А process() будет выглядеть так:


void process(char *data) 
{
  memset(data, 0xff, 256);
}

Это что, еще 2 ошибки, которые легко совершить получается? (читать с удивленно саркастичной интонацией)
Пойду напишу статью "Стотыщ и одна ошибка которую легко совершить не думая"

Это моя первая статья. У кого-то получается лучше, у кого-то хуже. Соглашусь, что можно детальнее разобраться и очень хорошо подумать, чтобы написать статью. Но, если ты ничего не напишешь, то и не получишь отклика или фидбэка. А так я получил с вами обратную связь, и теперь могу подумать над ошибками. Спасибо за комментарий. Я добавил пояснение насчёт process.
Ну статья для новичков конечно, но она про программирование, про не сразу видные подводные камни Си. Но это же хабр, тут не только матерые профи обитают, зачем так резко минусить?
Лучше своих ошибок напишите в комментариях. И давайте не будем про «кто в 21 веке пишет на С», поверьте пишут, там где ресурсов минимум или ассемблер или С — такой выбор.
И чему может научить новичка автор, который в первом же примере в сообщение пихает лишний байт '\0'? Длина строки-то уже определена!
ГЫ. дальше не читал!

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

Хаб "Системное программирование", ага.
Как страшно жить… ©

И часто ли используется в таком явном виде нулевой байт в середине строки? ;-)
По второму вопросу. Надо передать строку, в которой указано её длина, но так, чтобы не было некорректных символов, например нуля.
Я бы так сделал.
Записываем её.
sprintf(str_new, "%08X%s", strlen(str), str);
8 первых байт, это длина строки. В шестнадцатеричном виде.
Читаем её.
len = strtol(str_new, &str, 16);
В результате len длина строки и str указатель на саму строку.
Хотя нет. Лучше для чтении длины строки, скопировать в буфер 8+1 байт, и затем преобразовать. А то, если в строке числа есть, могут быть проблемы!
8 первых байт, это длина строки. В шестнадцатеричном виде.
4 миллиарда символов в строке? Я сейчас посмотрел одну из длинных книг, там всего около миллиона символов, и то в одной строке это хранить никто не будет. Так что хватит и 16-битной длины (4 цифры). Ну а преобразовать в число несложно:
len = 0;
if(str[0] < 'A')len += (str[0]+0x0A-'A')<<12; else len += (str[0] - '0')<<12;
if(str[1] < 'A')len += (str[1]+0x0A-'A')<< 8; else len += (str[1] - '0')<< 8;
if(str[2] < 'A')len += (str[2]+0x0A-'A')<< 4; else len += (str[2] - '0')<< 4;
if(str[3] < 'A')len += (str[3]+0x0A-'A')<< 0; else len += (str[3] - '0')<< 0;

Но все равно так делать не стоит. Если уж хочется строк в стиле С++, так и используйте std:string или хотя бы в структуру заворачивайте.
А это уже зависит от требований к софту. Например, согласно документации СУБД PostgreSQL, максимальная размер значения текстового типа — 1 GB. И оверхед на хранение значения больше 126 байт — 4 байта (там всё-таки длина не в hex хранится).
Интересно написано, хоть и достаточно базовые вещи. Программирую на PHP, но первые три знаю :)
Конечно, никто так явно не вставляет нулевой символ посередине строки. Но что если мы решили разработать свой протокол со своим форматом сообщений для обмена данными между удалёнными хостами?
Если человек решил разработать свой протокол на Си, то он наверняка будет знать о нулевом символе и о том, как он влияет на поведение некоторых функций (строковых в основном)

Т.к. тип char занимает в памяти ровно один байт, то логично, что для хранения длины сообщения, представленного типом long int мы бы зарезервировали для него первые четыре символа в массиве символов char[], или четыре ячейки блока данных, на которые указывает указатель char * ptr.
Не факт, тип long в разных системах занимает разное кол-во байт

Вообщем не статья а сюр какой-то.
Конечно, размер типа данных зависит от архитектуры машины. Я брал размер типа из стандарта. Уже поправил, указав ссылку на стандарт в самом начале. И, конечно, как вы сказали, если человек разрабатывает свой протокол на Си, он об этих вещах знает. Но человек может и не знать, если вообще никогда не писал свой протокол, а только впервые столкнулся с задачей упаковки и пересылки данных.
Уже поправил. В режиме совместимости с 32-битным ПО, тип long может занимать 4 байта (даже если сама операционная система и процессор 64-битные, но работают в данном режиме). Я тестил на Windows 8.1 и на Ubuntu 14.04. Процессор и системы у меня 64-битные. На Windows было 4 байта. А вот на Ubuntu 14.04 размер типа данных был 8 байт.
>Функция sleep определена в заголовочном файле <unistd.h>
Расскажите это девелоперам на visual studio.
В зависимости от операционной системы на которой программируешь софт, она может лежать в разных заголовочных файлах и иметь разные прототипы.
Sign up to leave a comment.

Articles